普通视图

发现新文章,点击刷新页面。
昨天以前首页

[视频攻略] Betterified VI – Bestified 以及闲扯(附游戏下载)

作者 石樱灯笼
2024年10月6日 23:20

比录视频更困难的是剪视频,比剪视频更困难的是传视频。

vlcsnap-2024-09-21-01h22m32s292-1_2x-1

  • 注1:本文不是攻略内容。本文为制作此视频攻略的闲扯。
  • 注2:文内视频引用为 Youtube 的视频,如您无法观看,请自行找办法解决。
  • 注3:游戏文本为英语。如您无法阅读英语,请自行找办法解决。
  • 注4:为什么做视频攻略而不是文字攻略或图文攻略?因为懒!

攻略视频链接

Youtube: [SMBX2] Betterified VI – Bestified – walkthrough: https://www.youtube.com/playlist?list=PLDwDrQvfAje57psgPq4Ih7nQGrVlx-sqF

如果你只是想看看游戏的视频,确认一下自己想不想也玩一下这个游戏,这里有几个国内可以直接访问的B站视频链接:

B站的那个播放列表我上传到20几个视频后就放弃了,实在是不舒服。你对付看也行。后面的内容才更有趣,但是我不打算再上传到B站了。

(突然意识到这游戏前面几关的Boss怎么都是银河战士的)


游戏引擎SMBX2

机缘巧合下看到别人玩的 Betterified VI – Bestified 这游戏的直播片段,感觉挺好玩的,毕竟这几年的传统的2D马里奥游戏,同质化已经让人厌倦得连任天堂都把传统2D马里奥引擎扔了重做了一个Wonder。

游戏引擎是 SMBX2 。

说到游戏引擎,SMBX2 的前身一看就知道是 SMBX 。我电脑里还有个 2010年版本的 SMBX1 引擎。这玩意曾是大名鼎鼎的马里奥同人游戏引擎,2015年任天堂才作出 Mario Maker,而 2010 年那阵,玩家制作马里奥同人基本就是 SMBX 。

只不过搜了一下,1.2 版本时期,相关论坛因管理员问题,变得极为狗血。当然了,很多游戏社区都有各种狗血问题和隐患。后来还有任天堂下场叫停,总之很坎坷。

SMBX2 基本上算是 SMBX 1.3 在 2015年的一个 Fork,现在已经开源 TheXTech/TheXTech

代码基本上是用 C++ 重写的 VisualBasic 6,按维护者自己的说法也是,代码质量很烂,毕竟 VB6 时代根本没什么正经人重视代码质量。

游戏内容的语言则是 lua ,理论上是个比 Python 屁事少很多的语言。但是,没有括号全靠自觉缩进来肉眼「测量」代码块这事情我是坚决不能接受,尤其是项目里又有空格又有TAB而lua本身对代码块并没有强制要求导致其代码质量有时候比Python还烂。

不过抛开引擎开发,这个引擎应该是可以作出远超过 马里奥制造2 的好游戏,只不过开发复杂度同样很高。毕竟本身同人免费游戏开发是一件非常费力不讨好的事情,很少能有人用心去做。


游戏 Betterified VI – Bestified

Betterified 应该是一个大系列,看 Release Trailer 可知作者做了许多作品,仅 Betterified 就包括6作,属实不容易。

当然我也没精力全玩一遍,前作到底是个几十分钟就能搞定的小同人,还是得肝几十个小时的超级大作,我也不知道剧情是不是连着的。

从八月初玩到九月末,可以说这游戏值得玩。难度适中,也有各种各样的降低难度的方式。根据网上的评价,这游戏的关卡机制和Boss战机制,作为一个同人马里奥游戏就是天花板。部分关卡和Boss战,放到整个游戏界都算是超一流超优秀的设计。除此之外,也有纯爽玩关卡和纯搞怪关卡。当然了,也是有纯粹恶心人的粪关卡的,可能作者就是想气一下你的这种感觉。尤其是第一关其实就很气人,感觉就是那种开场先警告你玩不起别玩的劝退关卡。

vlcsnap-2024-09-19-22h36m25s575

vlcsnap-2024-09-22-23h18m01s077

vlcsnap-2024-09-18-20h09m45s882

screenshot_on_b85m_by_flameshot_at_2024-09-11_02-16-08


录制视频

一开始是想在B站一边直播一边录制。但是B站的那直播环境实在是不怎么地。(尤其是这游戏在国内根本搜不到)

解谜关卡有时候需要相当多的思考时间,很多高难关卡还要很多专门试错的尝试(此游戏没有生命数限制,没有官方马里奥那种GAME OVER就得从头或者从大关第一关开始的设定,可以在当前关卡一直玩下去,直到通关),这也意味着废稿会非常多,而废稿存在硬盘里毫无意义,但直播又没法很方便的丢弃废稿。所以最后就放弃直播了,改为安心游戏了。不去关心什么之后,专心玩游戏录视频后,整体效率快了非常多。而且由于直播必须是连续的,而自己玩的话可以随时控制时间,游戏压力也小了很多。

Betterified VI – Bestified 我肝了一个多月,剪出来的攻略视频超过17个小时,视频原稿有多长更没法统计了,录制盘容量根本不够用,已经删了一堆了,剩下的没删的还有30多个小时。是的,带解谜而谜面又极其晦涩或者根本没有谜面的游戏就这么恶心,你就得到处跑,有些元素第一次找到的时候和再次找到的时候给的玩意都不一样,生怕误打误撞把第一次就这么给趟过去了,所以大部分原稿都是一直留到杀青了才敢删。

screenshot_on_b85m_by_flameshot_at_2024-10-05_01-38-12


制作攻略

Betterified VI – Bestified 这游戏秘密太多,而且也不知道作者是有意还是无意的,塞了一堆根本没头绪的隐藏秘密也就算了,还在特明显的地方塞了一堆根本没实现的解谜要素。

我先是自己不看攻略过了一遍,然后再看 Youtube 上其他玩家的视频。他们的视频基本就是随玩随录的,所以想找一个特定关卡特定位置的秘密特别困难。尤其是到游戏后期的秘密的提示或解锁位置是前面的关卡,翻来翻去超级困难。

最后就是自己看游戏的 lua 文件。然后发现,我自己是真的很暴力,直接用IDE,也就是我现在正在用的 Pulsar 编辑器搜关键代码,把整个游戏扒了一遍,基本每个能在游戏内看到的解谜要素,我都缕了一遍对应关卡的代码。

vlcsnap-2024-09-23-20h18m37s153

screenshot_on_b85m_by_flameshot_at_2024-10-05_01-59-51

基本上可以说我做的这一份视频攻略能做到此游戏的解谜要素全互联网最高覆盖率!


制作视频

做视频这一块我的原则是能不二次编码绝不二次编码,毕竟带宽只是时间,反正所有线上视频网站都会二压视频,所以我没必要浪费我的 CPU/GPU 时间。

H.264/MPEG-4 AVC 视频(以下接以AVC代称)的无损切割我是专门研究过的,用图形化的 Avidemux 就能轻松搞定。如果出现无损切割有困难实现不了的情况,就仅编码需要编码的小切片,一般也只有2秒钟左右。

screenshot_on_b85m_by_flameshot_at_2024-10-06_22-19-55

然后是拼接视频。如果一个原稿能直接无损剪出来,就不需要拼接。但是游戏打到后期,解谜的地方就越攒越多,就会有多个原稿关联到同一个解谜要素的情况,这种是躲不掉的,就必须把多个原稿的片段剪出来,然后用 FFMPEG 再贴到一起。其实基本上也就是个硬盘读写时间,都是眨眼就能完成,根本没什么难度。

screenshot_on_b85m_by_flameshot_at_2024-10-05_00-41-08

其实要说这一块,首先是 AVC 视频的 无损切割 ,当时刚开始学做视频的时候,我是研究过 AVC 编码技术的,其本质就是个 PNG 图片后面接上个运动补偿算法。如果想用大白话讲的话我可以再写一片文章。

无损拼接视频也是 AVC 视频的一种特性。但把多个视频贴到一起虽然有图形化界面的 Avidemux 应该能做到,但实际上总莫名其妙出错。Avidemux 编码器基本上也是 FFMPEG 套皮,但是对视频源要求太多,并没有 FFMPEG 那么野性,FFMPEG 报错也很野性,图形化界面根本控制不了。而且多个视频拼接基本上就是选一堆文件,这个在图形界面上反而效率太低,不如脚本来的快。所以拼接最安心的办法还是用 FFMPEG,而 FFMPEG 对于多个视频,用一个文本文件做文件列表最方便,但其 concat 又需要一个指定格式,所以最靠谱的方法是准备一个文件列表,然后命令行里中途生成一个临时文件,这就又需要新知识点 mktemp

你看,切个视频搞出来俩知识点,能写两篇文章。做视频真有意思。

这其间还遇到个一个诡异的情况,我整个过程中并没有修改过OBS的编码器,但是在某个时间段,输出文件的编码参数竟然还变了。

screenshot_on_b85m_by_flameshot_at_2024-09-16_01-21-47

诡异。

说尴尬的地方就是,这其间其实遇到过不少专门从国外往国内搬视频的「所谓大佬」。那种拿着 bandicam 或 Fraps 带着水印对着浏览器录屏的烂操作也就算了,巨大个视频抠下来,只为了掐头去尾,先搞各种「大神」工具,然后压崩,又专门整个 Adobe Premiere 把视频重新压一遍,再漫不经意来一句「又不费时间」。

顿时又觉得做视频没意思了。


上传视频

其实做这攻略的中途我是有打算放弃的,因为这游戏有些解谜实在太晦涩了,好烦。感觉整个流程最闹心的步骤大概就是这一块了。但是好歹最后咬牙挺过来了,而且把游戏做到全解密(至少自己能做到的全做了),还整了成了视频,做完了之后成就感还挺高的。再加上是互联网上并没有针对这个游戏的任何攻略资料,只有几个高玩随便玩玩时录的视频,并没怎么整理成流程攻略,所以新玩家如果卡关了,在网上是无法直接搜到对应的攻略的。这让我成就感更高了。

但是我却确实是没想到这才是整个过程中最痛苦的阶段。

我最初的想法就是玩一部分后就上传一部分,但是玩到游戏中期就发现这游戏总出现后面关卡的谜面对着的是前面关卡的谜底,也就是说前后关联性特别密切,先上传的视频很可能出现误导性,作为整个解谜攻略来讲,早期的稿件很可能变成大坑,是要删除的。虽然回头玩一下先前关卡也就几分钟的事情,剪切也就是跟着新稿一起剪,但是上传以及写描述这个阶段在整体上的占比就太大了,不如索性先不要上传,把游戏整体都打通之后确保不会把废稿上传上去后,一起上传最好。

然后恶心的地方就来了。我最初是想B站和Youtube都上传的,先上传B站。但是B站的那个破机制实在是恶心。同一天大量上传稿件就会被降权,有传闻一天最好不要上传超过6个视频。我特么有170多个视频,天天上传视频我得上传一个月。再加上B站的那个网页端烂得要死,视频展示页面就更烂得要死(描述会被截断,所以你会发现很多人把视频描述写在置顶评论里),去他的破逼站吧,爷不伺候了。

(做B站的UP主,算是打白工还是打黑工?)

那就传 Youtube。Youtube 的上传系统倒是正常的,没啥大毛病,但确实你得完全自己做缩略图,Youtube 的封面缩略图功能基本还是10年前的逻辑,除非你整个视频上传完,否则不能生成缩略图,缩略图也不能线上编辑,这点也算能忍。然后发现 Youtube 的定时发布的功能设计也是10年前的。甚至最后你能发觉 Youtube 的所有功能都是行业10年前的设计,基本跟行业都脱节了,除了一堆生怕被欧盟和少数派团体暴打的破选项玩意之外,其他功能都是没跟上时代步伐,基本是停留在2014年了。

然后就是相当漫长的写描述做封面的阶段。这比打游戏剪视频痛苦多了。

screenshot_on_b85m_by_flameshot_at_2024-10-05_01-56-11

而且由于是攻略,我得考虑好各种 SEO 因素,至少要保证如果有人搜关键词,是要能搜到我的视频,然后看到我做的封面图之后,能确定这是相应视频,点进来。

screenshot_on_b85m_by_flameshot_at_2024-10-05_01-58-35

整个上传流程共计用掉了2个星期,太长了。有些视频切完了放在硬盘里好几天,等要上传,或者都已经上传完了之后,才发现视频内容有误,然后翻原稿,重新剪,重新传,有时候真得叹口气庆幸好在当时的原稿没有被删除。录制盘是我老笔记本的拆机盘,一共才120G,其实已经吃紧非常久了。

screenshot_on_b85m_by_flameshot_at_2024-10-05_02-06-55

最后共计172个视频上传完成,累够呛。

我开始玩游戏的时候这游戏是 v1.0.9.1 版本,等我上传完视频,这游戏都更新到 v1.0.11.1 了。作者回复我说只是修了些Bug,我自己对比了代码,有一些看不懂的改动,但是不影响我视频的内容,也就这样吧,这事就完结了。

(其实我的游戏流程离理论完美还差一小段,但是我不想继续做下去。至少现在不想)


游戏下载

假如你要入坑的话:

官网:[SMBX2] Betterified VI – Bestified + FULL SMBX EASY INSTALL VERSION

官方提供一个游戏内容包,可以自行添加到你已经安装的 SMBX2 引擎之中。官方也提供一个二合一包,不需要自己手动安装 SMBX2 。官方还提供一个增量升级包,是给有 Betterified VI – Bestified 1.0 用户用的,如果你已安装旧版本,可以用增量包升级更新。

官方提供的下载链接都是 Google Drive 的。如果你无法顺利访问 Google Drive ,这里提供 InfiniCLOUD 网盘的 Betterified VI – Bestified v1.0.11.1 游戏内容包和 SMBX2+Betterified VI 二合一包。

文件太大,我就不上传微云了,没空间了。


结语

也算是了了一件心事。

The post [视频攻略] Betterified VI – Bestified 以及闲扯(附游戏下载) first appeared on 石樱灯笼博客.

从参与 Rust 标准库开发看开源贡献的源动力

作者 tison
2024年8月9日 08:00

首先介绍一下我在 Rust 标准库当中做的两个微小的工作。

第一个是从去年 8 月 14 日发起,今年 4 月 6 日合并,历时约 8 个月,目前仍在等待 stabilize 的为 OnceCellOnceLock 增加新接口的提案:

第一次提交贡献

第一次贡献成功合并后,马上第二个工作是从今年 4 月 8 号开始,7 月 6 号合并,历时约 3 个月,同样还在等待 stabilize 的为 PathBufPath 增加拼接扩展名新接口的提案:

第二次提交贡献

可以看到,这两次贡献的内容可算是同种类型的,第二次提交从发起到合并的时间比第一次缩短了一半以上。本文先介绍 Rust 标准库提案的基本工作流程,然后介绍这两个贡献背后的故事,最终讨论开源贡献的源动力从何而来。

Rust 标准库提案的工作流程

标准库的贡献有很多种,上面我所做的两个贡献都是扩充现有数据结构的接口,也就是 API Change 类型的提案。

Rust 社群为 API Change Proposal (ACP) 设计了专门的流程,流程文档记录在 std-dev-guide 手册中。

简单来说,一共分为以下三个步骤:

  1. rust-lang/libs-team 仓库中创建一个新的 ACP 类型的 Issue 开始讨论;
  2. 讨论形成初步共识以后,修改 rust-lang/rust 仓库中标准库的代码并提交 PR 开始评审;
  3. 针对实现达成共识以后,在 rust-lang/rust 仓库中创建对应的 Tracking Issue 并由此获得标注 unstable feature 需要的参数(Issue 编码);PR 合并后,由 Tracking Issue 记录后续的 stabilize 流程

不过,其实我所做的两个简单改动并没有严格遵循这个流程。因为我首先不知道这个流程,加上实际改动的代码非常少,我是直接一个 PR 打上去,然后问一句:“老板们,这里流程是啥?我这有个代码补丁还不错,你看咋进去合适。”

开源贡献的源动力从何而来

为什么要给 OnceCell 和 OnceLock 加接口?

真要说起来,给 OnceCell 和 OnceLock 加接口的起点,还真不是直接一个 PR 怼脸。上面 PR 截图里也能看到,首先是我在原先加入 once_cell 功能的 Tracking Issue 提出为何没有加这两个接口的问题:


进一步地,之所以会提出这个问题,是我当时在实现 Apache Kafka API 当中 RecordBatch 接口,其中涉及到一个解码后缓存结果的优化。因为 Rust 对所有权和可变性的各种检查和契约,如果想用 OnceCell 来实现仅解码(初始化)一次的效果,又想要在特定上下文里取得一个可变引用,那就需要一个 get_mut_or_init 语义的接口。

实际上,这个接口的实现跟现有的 get_or_init 逻辑几乎一致,除了在接收者参数和结果都会加上 &mut 修饰符以外,没有区别。所以我倾向于认为是一开始加的时候没有具体的需求,就没立刻加上,而不是设计上有什么特殊的考量。

于是,开始讨论后的下一周,我就提交了实现功能的代码补丁。很快有一位志愿者告诉我应该到 rust-lang/libs-team 仓库走 ACP 的流程,我读了一下文档,尝试跟提出这个要求的志愿者 confirm 一下具体的动作。不过马上就是两个多月的毫无响应,我也就把这件事情暂时搁置了。

经典开始等待……

11 月,两位 Rust 语言的团队成员过问这个补丁。尤其是我看到 Rust 社群著名的大魔法师 @dtolnay 表示这个补丁确实有用,应该合进去以后,我感觉信心倍增,当周就把 ACP 的流程走起来了。

随后就是又两个月的杳无音讯……

今年 1 月,@dtolnay 的指令流水线恰巧把这个 PR 调度上来,给我留了一个 Request Changes 让我修一下编译。发现我凭直觉写出来的合理代码遇到了另一个 Rust 编译器的 BUG 导致编译不过:

解决以后,社群志愿者 @tgross35 指导我给新的功能添加 unstable feature 的属性注解,也就是:

1
#[unstable(feature = "once_cell_get_mut", issue = "121641")]

另一位社群志愿者 @programmerjake 帮我指出几个文档注释的问题,为标准库添加新接口不得不写好接口文档,摆烂不了。

在此过程中,我给 OnceLock 也同样加了 get_mut_or_initget_mut_or_try_init 接口,因为它和 OnceCell 本来就是同期进来的,接口设计也一模一样。我想那就一起加上这个接口,免得 OnceLock 后面有同样的需要还要另走一次 ACP 流程。

这悄然间又是两个月过去了……

终于,在 @programmerjake Review 完之后一周,@dtolnay 出现并 Appove 后合并了代码。

第一次 Rust 主仓库参与贡献至此告一段落,唯一剩下的就是等 libs-team 主观认为合适之后启动功能 stabilize 的流程。

上个月,有人发现之前写的文档注释还是有错误,于是提交 PR 修复。这就是某种开源协同让代码质量向完美收敛的过程。

为什么会想到给 Path 加接口?

这个动机要追溯到我用 Rust 重写 License Header 检测工具 HawkEye 的时候了。

Rust 重写 HawkEye 的动机,则是为了支持调用 Git 库跟 gitignore 的机制做集成。虽然原先 Java 的实现可以有 JGit 来做,但是 JGit 的接口很脏,而且加上 JGit 以后就不能 Native Image 了导致分发产物体积显著变大。虽然都不是什么大事,但是工具类软件本来就是强调细节处的开发者体验,正好我逐渐掌握了 Rust 编码的技巧,就拿上来练练手。

今年 3 月重写 HawkEye 的时候,我发现了 Rust 标准库里 Path 结构缺一个接口:

1
2
3
4
let mut extension = doc.filepath.extension().unwrap_or_default().to_os_string();
extension.push(".formatted");
let copied = doc.filepath.with_extension(extension);
doc.save(Some(&copied))

我在写上面这段代码的时候,目的是给文件名加一个 .formatted 后缀。但是,Path 的 with_extension 接口是直接替换掉扩展名,也就是说,原本我想要的是 file.rs 变成 file.rs.formatted 的效果,如果用 with_extension 接口,就会变成 file.formatted 这样不符合预期的结果。

最终,只能用上面这样的写法手动绕过一下。显然代码变得啰嗦,而且这段代码是依赖 extension 的底层实现细节,某种程度上说是 brittle 的。

遇到这个问题的时候,我就想给标准库直接提一个新的 PR 了。不过当时 OnceCell 和 OnceLock 的 PR 还没合并,我拿不准 Rust 社群的调性,也不想开两个 PR 都长期 pending 下去。

好在上一个 PR 在几周后顺利合并了,我于是同时创建了 ACP 和 PR 以供社群其他成员评审。因为改动面很小,比起口述如何实现,不如直接在 ACP 里链接到 PR 讲起来清楚。

这次,提案很快得到 Rust 团队成员,也是我的前同事 @kennytm 的回应。不到两周,我就按照上次的动作把 PR 推到了一个可以合并的状态。

然后又是两个月的杳无音讯……

最后,我根据其他社群成员的指导,跑到 Rust 社群的 Zulip 平台上,在 libs-team 的频道里几次三番的问:“这个 ACP 啥时候上日程啊?”

终于,在今年 7 月初,libs-team 在会议上 appove 了 ACP 的提议,并在一周内再次由 @dtolnay 完成合并。

开发者的需求是开源贡献的源动力

可以看到,上面两个贡献的源动力,其实都是我在开发自己的软件的时候,遇到的 Rust 标准库缺失的接口,为了填补易用接口的缺失,我完成了代码开发,并了解社群流程以最终合并和发布。在最新的 Rust Nightly 版本上,你应该已经能够使用上这两个我开发的接口了。

在开源共同体当中,一个开发者应该能够发现所有的代码都是可以更改的。这是软件自由和开源定义都保证的事情,即开发者应该能够自由的更改代码,并且自由地使用自己修改后的版本。

那么,开发者在什么时候会需要修改代码呢?其实就是上面这样,自己的需求被 block 住的时候。小到 Rust 标准库里接口的缺失,大到我之前在 Apache Flink 社群里参与的三家公司合作实现 Application Mode 部署,甚至完全重新启动一个开源项目,说到底核心的源动力还是开发者真的对这样的软件有需要。

因此,开发者的需求是开源贡献的源动力。一旦一个开源软件不再产生新需求,那么它就会自然进入仅维护状态;而一旦一个开源软件不再能够满足开发者实际的需求,甚至开发者连直接修改代码的意愿也没有,那么它的生命也就到此为止了。

Logforth: 从需求中来的 Rust 日志功能实现

由此,我想介绍一个最近在开发 Rust 应用的时候,由自己需求出发所开发的基础软件库:

Logforth: A versatile and extensible logging implementation

Rust 生态的日志组件,由定义 Logging 接口的 log 库和实现 Logging 接口的各个实现库组成。其中,log 库可以类比 Java 日志生态里的 SLF4J 库,而日志实现则是 Logback 或 Apache Log4j 这样的库。实际上,Logforth 的名字就来自于对 Logback 的致敬。

编写 Logforth 的动机,是我在选择 Rust log 实现的时候,现有所有实现都不满足我的灵活定制需求。生态当中实际定位在完整 log 实现的库,只有 fern 和 log4rs 这两个。其中,fern 已经一年多没有新的发布了,一些显而易见的问题挂在那里,内部设计也有些叠床架屋。而 log4rs 则是对 Log4j 的直接翻译,也有许多非常诡异的接口设计(底层问题是 Java 的生态和设计模式到 Rust 不能直接照搬)。

于是,我花了大概两个小时的时间,梳理出 Rust 和 Java 日志生态里日志实现库的主要抽象:

  • Appender 定义日志写出到目标端的逻辑;
  • Filter 定义日志是否要打印的逻辑;
  • Layout 定义日志文本化的格式。

然后花了一天的时间,把 log4rs 的主要 Appender 即标准输出和(滚动)文件输出给实现了,同时完成了基本的基于日志级别的 Filter 和纯文本以及 JSON 格式的 Layout 实现,就此发布的 Logforth 库。

经过两周的完善,目前 Logforth 库已经具备了全部日志实现所需的基本能力,且可以自由扩展。接口都非常干净,文档也都补齐了。除我以外,一开始一起讨论需求的 @andylokandy 也极大地帮助了 API 的设计跟多种常用 Appender 的实现和优化。应该说,目前的 Logforth 已经是超越 fern 和 log4rs 的库了。

而回到本文的主题,之所以我能在一天时间内就写出第一个版本,我跟 @andylokandy 能目标明确、充满动力地实现 Logforth 的功能,就是因为看到了自己和整个 Rust 生态的需求,并且我们清楚应该怎么实现一个足够好的日志库。

Rust 生态的诸多潜在机会

最后,为坚持到这里的读者分享几个我看到的 Rust 生态潜在的机会。

总的来说,目前整个 Rust 生态接近 Java 1.5 到 1.7 时期的生态,即语言已经流行开来,核心语言特性和标准库有一些能用的东西,整体的调性也已经确定。但是,距离 Java 1.8 这样一个全面 API 革新的版本还有明显的距离,核心语言特性的易用性有很大的提升空间,标准库的 API 能用但算不上好用,开源生态里开始出现一些看起来不错的基础库,但是还远远没有达到 battle-tested 的状态。

第一个巨大的缺失点就是 Async Rust 的实用工具。我找一个 CountdownLatch 的实现,找了半天才发现去年底发布的 latches 库符合我的期待。要是去年我有这个需求,就真没人能搞定了。后来我要找一个 Async 版 WaitGroup 的实现,找来找去没有一个合适的,只能自己 fork waitgroup-rs 改改来用。

Crossbeam 是个很不错的高质量库,可惜它提供的是同步版本的并发原语,不能在 Async Rust 里使用。上面 latches 和 waitgroup-rs 单看实现得还可以。但是这种单文件库,真的很难长期维护,而且像 latches 这样一个结构硬整出各种 feature flags 的做法,其实是反模式的,没必要。

所以一个 async-crossbeam 可能是目前我最想看到的社群库,或许它可以是 futures-util 的扩展和优化。这些东西不进标准库或者事实标准库,各家整一个,真的有 C++ 人手一个 HashMap 实现的味道了。

第二个缺失点,顺着说下来就是 Async Runtime 的实现。Tokio 虽然够用,但是它出现的时间真的太早了,很多接口设计没有跟 Async Rust 同步走,带来了很多问题。前段时间 Rust Async Working Group 试图跟 Tokio 协商怎么设计标准库的 Async Runtime API 最终无疾而终,也是 Tokio 设计顽疾和社群摆烂的一个佐证。

async-std 基本已经似了,glommio 的作者跑路了,其他 xxx-io 的实现也有种说不出的违和感。这个真没办法,只能希望天降猛男搞一个类似 Java ExecutorService 这样的体系了。而且最好还要对 Send + Sync + 'static trait bound 做一些参数化,不用全部都强行要求……

IO 和 Schedule 虽然有关系,但还是不完全一样的概念。总的来说 Rust 生态里暂时没有出现跟 Netty 一样 battle-tested 的网络库,也没有能赶上 ExecutorService 生态的 battle-tested 的异步调度库。不过好的调度原语库还是有一些的,比如 crossbeam-deque 和 soml-rs 里的相关库,等待一个能合并起来做高质量 Runtime 的大佬!

第三个缺失点还是跟 Tokio 有关。应该说 Tokio 确实做得早而且够用,但是很多设计到今天看就不是很合适了,然而社群里用得还是很重,就变得更不好了。比如上面的 log 库,Tokio 生态里搞了个叫 Tracing 的跟官方 log 库不兼容的接口和实现,也是一大堆槽点。

这里想说的是 bytes 库。很多 Rust 软件闭眼睛就用 bytes 处理字节数组,这其实有很大的性能风险。例如,基于 bytes 搞的 PROST 就有一些莫名其妙的的多余拷贝,可参考《Rust 解码 Protobuf 数据比 Go 慢五倍?》。值得一提的是,PROST 也是 Tokio 生态的东西。

Apache OpenDAL 内部实现了一个 Buffer 抽象,用来支持不连续的字节缓冲区。这个其实也是 Netty 的 ByteBuf 天生支持的能力。如果现在 Rust 有一个 Netty 质量的网络库,有一个 ExecutorService 的接口跟一些基本可用而不像 tokio 一样全家桶的实现,再把文件系统操作搞好点,我想整个 Rust 生态的生产力还能再上一个台阶。

Rust 的 Async FileSystem API 实现现在没有一个能真正 Async 的,这个其实 Java 也半斤八两。不过 Rust 的 io::copy 不能充分利用 sendfile syscall 这个,就被 Java 完爆了。这点是 Apache Kafka 实现网络零拷贝的重要工程支撑。

最后一个,给国人项目做个宣传。Rust 的 Web 库也是一言难尽,同样是 Tokio 生态的 Axum 设计让人瞠目结舌,整个 Rust 生态对 Axum 的依赖和分发导致了一系列痛苦的下游升级体验,可参考 GreptimeDB 升级 Axum 0.7 的经历,至今仍然未能完成。

国内开发者油条哥搞的 Poem 是更符合 Web 开发习惯的一个框架,用起来非常舒服,也没有奇怪的类型挑战要突破。Poem 的最新版本号是 3.0.4 而不是像 Axum 的 0.x 系列,这非常好。

不过,Poem 的接口文档、回归测试跟一些细节的易用性问题,还需要更多开发者使用慢慢磨出问题跟修复。希望 Poem 能成为一个类似 Spring 的坚固 Web 框架,这样我写 Rust 应用的时候,也能省点心……

举个例子,我的一个 Poem 应用里有 query handle panic 的情况,就踩到了 Poem 一个并发对齐的设计没有处理 panic 的缺陷。当然,我顺手就给修了,所以新版本里应该没这个问题。

至于文档不全的例子,主要是错误处理的部分,应该需要更多最佳实践。

无论如何,目前 Rust 生态整体的生产力和生产热情,还是比 Java 要高出太多。长期我是看好 Rust 的发展的,短期我只能安慰自己 Netty 是一个在 Java 1.6 才出现,1.8 才开始逐渐为人熟知使用,5.x 胎死腹中,发展时间超过 15 年的项目,生态要发展并不容易了。

CommunityOverCode Asia 2024 参会纪实

作者 tison
2024年7月30日 08:00

CommunityOverCode Asia 2024 于 7 月 26 日到 28 日在杭州举行。其前身是 Apache 软件基金会一年一度的 ApacheCon 活动,通常会在多地分别举办。今年已经举行的是本次亚洲大会,以及 6 月在斯洛伐克举行的欧洲大会。10 月,北美大会将在丹佛举行,我也会在北美的会议上分享两个主题:

  • Mobilize Your Community Army: A Commercial OpenSource’s Perspective
  • Equip the Community with Test Suite: Best Practice from Apache OpenDAL and more

欢迎届时关注。

本文分享我在刚刚过去的 CommunityOverCode Asia 2024 大会上的见闻和体会。

会场体验

本次活动现场,主要分成以下四个部分:

  1. 主会场
  2. 各个分论坛的会场
  3. Workshop
  4. 开源集市等自由活动区域

Workshop 我没进去,不知道具体什么情况。

各个分会场的空间今年是有点狭小了,即使全部坐满,估计也就四十个人的水平。同时,狭小的空间里开猛烈的空调,相对来说更容易犯困或身体不适。我在各个分会场里都没有待太久的时间。

Community 分会场由于有几位海外讲师,并且确实到场的外国友人比较多,是唯一有文字同传的分会场。但是文字同传需要视线在讲师、PPT 和翻译软件上来回切换,且翻译有几秒钟的明显延迟,现场的外国友人普遍反应体验一般。

主会场是整个会场当中体验最好的一部分,屏幕比去年的屏幕看起来要大上很多。我想如果我在上面讲 Keynote 演讲,应该也不得不好好准备了(笑)。

巨大的主会场屏幕

最后,开源集市布置在一个狭小的过道上,整体体验上比较局促。不过参与展览的项目和组织倒是不错,我除了在 GreptimeDB 摊位上不定时刷新,也在 Apache 基金会摊位上客串大半个小时的 NPC 跟不少参会者做了交流。

Greptime 的文创广受好评

商业、开源以及出海

这次大会上我除了出品 Community 分论坛以外,主要参与分享了两个主题,分别是:

  • 第一天主会场的圆桌讨论《国际化的机遇和挑战》
  • 第二天 Community 分会场的主题分享《动员你的社群大军:商业开源视角下的开发者关系》

可以看到,两者围绕了商业、开源和出海等主题展开。

其中第一天的圆桌,本来我以为要讲一些比较 tough 的话题,比如开源社群国际化当中的挑战,商业开源出海如何取得成功,结果讨论的都是比较柔和的话题,包括应该什么时候做国际化,国内和海外参与者的不同,社群如何建设之类有定论的主题。整体气氛轻松欢乐。

社群成功的关键是及时响应;参与开源社群可以是轻松的

第二天的主题分享,由于事前我没有做好排练,外加分会场冷气 debuff 和我的演讲时间被压缩到了 15 分钟,整体分享效果应该说非常糟糕。我实际想说清楚的是:

  • 开源社群不同分类的动机及可持续的根基;
  • 商业开源的模式和商业与开源的共生关系;
  • 面向商业开源模式的开发者关系如何实施。

其中,我目前主要的观点是,持续的开源创造,要么是个人自己所需,自产自销;要么有其他收入来源,支付开源开发的成本。后者对于社群团队来说,就是募资和加入企业就职;对于商业团队来说,需要商业盈利,反哺上游开源软件,核心基础能力走开源孪生构建生态的同时帮助大众。大公司支持的企业开源,是特定的标准化工作能规避的巨大成本风险。

商业开源的理论蓝图

这个主题我会在 10 月北美的大会上再做一次英文演讲,应该到时候做好排练调整一下内容顺序,效果会更好一些。敬请期待到时的分享和专项说明文章。

此外,作为 Community 分论坛的出品人,本应由我担任的分论坛主持人一职,实际最后是 Ted Liu 和 Yu Liu 客串完成的。感谢两位的支持 :D

现场与场外的交流

虽然本次会场没有 Unconference 一类专门交流的节目,但是参会者在开源集市、会场休息处和其他场外地点做的交流可一点都不少。应该说,这样一场行业瞩目的开源峰会,来到现场的人在主题分享之外的交流,也是会议价值不可缺少的组成部分。甚至以其创造的价值总量而言,它是超过主题分享的。

夜话 ASF Culture

得益于来自中国的 ASF 董事姜宁老师亲自前往北美和欧洲的邀请,本次亚洲大会有很多外国友人参会和做分享。

由于我跟外国讲师恰巧都住在会场附近的酒店,在第二天晚上 Track Chair 聚会结束以后,我跟几位外国讲师回到酒店大堂边吃夜宵边聊起了在 ASF 的经历和各地文化的差异。

几位讲师都是游历各地的老江湖了,他们分享起各地的风俗和自己的人生经历,十分有趣。尤其是他们面对跟自己儿子乃至孙子同辈的我解释笑话里面的梗的时候,连比带划的介绍然后又想要跳过黄暴的部分,非常迫真。

线下交流会让每个人都发现,其实线上看起来一板一眼,只知道规则只关心编码的人,也是一个活生生的有自己生活的人。大家对生活的热爱和基本的品质是共通的,由此在线上产生矛盾的时候,也更容易站在对方的角度考虑其动机,而不是直接假定对方是故意搞破坏的恶人。

除了交流各地的文化和趣闻,作为资深的开发者和常年参与 ASF 事务的基金会成员,我们也交流了不少软件研发过程当中的经验、近期软件发展的趋势,以及对 ASF 文化、议事规则跟近期冲突争议事件的看法。我想,这就是 ASF 纪录片里提到的,ASF 最大的价值是其成员之间的共识,而这种共识是由每次具体的讨论反复达成和演进的吧。

在跟国内某开源软件基金会的朋友交流的时候,谈及 ASF 的流程和议事规则,我们都很惊讶 ASF 大量的步骤依赖于一个看起来很不可靠的“达成共识”。例如项目如何进入孵化器?只要孵化器的项目管理委员会(PMC)成员达成共识即可。进入孵化器的导师如何寻得?答案是只要项目足够有趣,不少 PMC 成员会志愿担任导师。孵化项目版本发布如何保证合规?几乎每时每刻都有导师志愿帮助检查,而且这种检查可能是非常严格的。

这种依赖志愿者的做法,并且数百名孵化器志愿者有着相似的共识,上万名 Committer 主观接受的理念,才是 ASF 有别于其他组织的核心竞争力。

虽然出于这次夜话 private 的性质,我没有留下合影。不过会场内单独的合影还是有的:

Craig L Russell

前几周去湾区的时候跟 Craig 吃了一顿泰国菜,听他吹牛在 IBM 和 Oracle 的工作经历,吹得太厉害忘记合影留念,这次可算补上了。

Justin Mclean

Justin 跟我介绍了几个他的拿手好菜。希望下次发版或者过提案的时候,别把我当成菜炒了 >~<

后起之秀的开源项目

大会现场有位中国开源的老玩家问我,是不是感觉每年参与开源大会的都是同一批人。我说或许主导国内开源方向,跟参与社群运营的主力军,这些年的中流砥柱都没有发生太大的变化,但是我作为 ASF 孵化器导师,不停地能看到新项目进来,还有新的开发者投身开源,从这个角度来讲,其实中国开源的新面孔还是非常多的。

今年让我印象深刻的后起之秀有这么三位。

第一位是在主会场做主题演讲的 Apache Fury 原作者杨朝坤(Shawn)。虽然他在分会场讲序列化框架的技术细节讲得那叫一个深入,但是在主会场上还是很好的把握了大众的需求,分享了一个 Side Project 如何从蚂蚁集团内部孵化,经历一次申请开源失败以后,通过一段时间的內源运营以后成功开源并进入 ASF 孵化的故事。

Apache Fury 的开源之旅

项目成功的故事是群众喜闻乐见的。Shawn 富有感染力的分享打动了现场的听众,Apache Groovy 的 PMC Chair Paul King 投喂了一个明知故问的技术问题,让 Shawn 有机会进一步展开作为跨语言 IDL-Free 的序列化框架 Fury 的设计优势。

Paul King 抛出“诱饵”问题

我客串了一把技术翻译

第二位是在主会场做闪电演讲,在 Community 分会场也做了分享的 Apache StreamPark Committer 张超(@VampireAchao)。作为一名 00 后程序员,他非常外向且思维活跃,编程技巧过关,假以时日应该会成为强大的开源开发者。

闪电演讲:Chrome 的 Debug 技巧

第三位是满会场宣传 Apache Amoro 的陈政羽。开源运营是一个富有挑战的领域,尤其是并非自己开发且独占主导权的项目,如何协调不同参与方的需求,跟核心开发者商量项目的定位和演讲方向,是非常不容易的。

经过本次会场讨论,Apache Amoro 当前的实质定位是增强 Apache Iceberg 的自动调优服务。一言以蔽之,Iceberg++ 是其核心价值点。不过这跟部分核心开发者希望兼容 Hudi 的负载,想开始超融合所有数据集成生态的目标有出入。我们可以拭目以待这个项目后续的发展,以及陈政羽在其中协调结果的成色🤣。

SelectDB 乱入 O_O

未来的开源峰会

CommunityOverCode Asia 2024 圆满结束。今年,国内还尚未开幕的开源峰会当首推开源社将于 11 月举办的中国开源年会(COSCon)。

定档 11.2-3,COSCon’24 第九届中国开源年会暨开源社十周年嘉年华正式启动!

巧合的是,今年是 Apache 软件基金会 25 周年,也是开源社成立的 10 周年。或许从未来回头看,今年会是中国开源继往开来的关键年😇。

此外,今年的 CommunityOverCode 大会还有 10 月的北美大会尚未举办。我将于 10 月到达丹佛会场做两个主题分享:

  • Mobilize Your Community Army: A Commercial OpenSource’s Perspective
  • Equip the Community with Test Suite: Best Practice from Apache OpenDAL and more

欢迎届时关注。

最后,去年和今年的 CommunityOverCode Asia 承办单位几乎都是贴钱办会,以至于活动组织者专门写了一篇文章“化缘”:

正在消亡的社区会议:呼吁更多伙伴持续支持阿帕奇亚洲大会的举办

我想,作为面向开发者的大会,最希望看到 CommunityOverCode Asia 顺利举办的,是每一位热爱开源的开发者。我向组织者建议,明年筹款的时候,其一可以实时公布筹款进度,让潜在的赞助者知道要想会议成功举办,还需要自己的多少支持;其二可以开通个人赞助的渠道,我想参与开源的开发者们众志成城,并不难凑齐这样一场年度盛会所需要的小几十万元人民币。

我在这里承诺,如果明年开通了个人赞助的渠道,我将以本人的名义或届时所在公司的名义,捐赠不少于十万元人民币以作支持。期待明年 CommunityOverCode Asia 越办越好!

Apache DataFusion 湾区线下聚会纪实

作者 tison
2024年7月15日 08:00

6 月 24 日,我在北美湾区参与了一场线下的 Apache DataFusion 聚会活动。

其实我是 6 月 21 日才到的旧金山,6 月 18 日才发现湾区有这样一场线下活动。不过或许得益于我在今年在 DataFusion 做过几次贡献,GreptimeDB 是 DataFusion 在行业顶级的应用标杆,会议组织方很干脆的就增加了一个 Speaker 席位,让我能够做在聚会上做题为《Boosting a Time-Series Database With Apache DataFusion》的演讲。

会议风格

本次 Meetup 一共报名 80 人,实际到场超过 50 人,绝对算是小型 Meetup 里的大型聚会了。现场是一位联合主办人的办公室,堪堪容纳所有人或站或坐待在室内。

我印象最深的是海外 Meetup 更加注重 Get Together 的风格,与国内 Meetup 基本是讲师从头讲到尾的风格的差异。

一般来说,国内的 Meetup 一个主题可以讲 30-40 分钟,4-6 名讲师讲完一个下午也就结束了。往往参与者也不怎么听完主题,而是逮到相关的到场专家,或者听到感性的主题以后,就拉着专家或讲师直接另开小会去了。

我刚报上名参加湾区 DataFusion Meetup 的时候,得到的消息是演讲时长 15-20 分钟,临了又改成 12-15 分钟。基本上一共六名讲师,从六点开讲,到七点半就完全讲完了。剩下的时间留给现场的观众自由交流,当然也有人拉着看对眼的与会者出去开小会,但是总体所有人都听完了所有主题,并且也还毫不疲倦。

这是国内不少活动最近有在参考的一个趋势。不管是年初 OSPO Summit 的 unconforence 环节,还是接下来几场 Apache 软件基金会的国内会议筹备的形式,都更加注重 Get Together 与会者自由交流,而不再是发布会形式的讲师从头讲到尾。

此外,海外的组织者对这样的小型聚会明显更有热情,他们有 lu.ma 和 meetup.com 等等丰富的宣传渠道,也乐于在湾区举办各种会议交流前沿进展。这点是国内近几年来声音比较弱的方面。

活动预告

在进入演讲内容回顾之前,先预告将在未来两周举办的两场 Apache 生态线下活动。

第一场是在 7 月 21 日下午杭州蚂蚁 A 空间举办的 Apache DataFusion Meetup 活动。GreptimeDB 核心工程师,同时也是 DataFusion 社群 PMC 成员夏锐航将会做深入的技术分享;蚂蚁集团工程师和 eBay 工程师也都将分享他们基于 DataFusion 构建出来的多样系统,参与 DataFusion 社群的故事,以及对 DataFusion 未来的看法。

欢迎点击链接( https://www.huodongxing.com/event/5761971909400?td=1965290734055 )报名,或扫描以下二维码报名。

第二场从 7 月 26 日开始持续 7 月 28 日。Apache 软件基金会年度活动 Community Over Code Asia 2024 今年也将在杭州举办。我将作为 Community 分会场的出品人参与活动并做演讲。GreptimeDB 的核心工程师,同时也是 OpenDAL 社群 Committer 的徐文康也将在 IoT 分会场上做技术分享。GreptimeDB 在现场另有展位介绍,我会全程在场(たぶん)。

欢迎通过这个链接( https://asia.communityovercode.org/zh/ )报名。

演讲内容

聚会上一共有八名讲师做了六个主题演讲,分别是:

  • LanceDB 的 CEO Chang She 分享了他们利用 DataFusion 实现向量计算的实践;
  • Cube.js 的 CTO Pavel Tiuvov 分享了基于 DataFusion 实现的查询缓存层;
  • DataFusion 的现任 PMC Chair Andrew Lamb 介绍了社群发展的近况和未来展望;
  • Denormalized 的创始人 Matt Green 分享了使用 DataFusion 实现流计算引擎的实践;
  • DataFusion 的原作者 Andy Grove 和他的同事分享了在 Apple 实现了 DataFusion Comet 做 Spark 查询加速实践;
  • 我介绍了 GreptimeDB 使用 DataFusion 和整个 FADOP 技术栈快速实现了一个高完成度的时序数据库的实践。

出于演讲时间限制,我的核心内容浓缩在了 GreptimeDB 如何使用和定制化 DataFusion 的实现上:

  • 每位分享嘉宾介绍的实践,都包含了在 DataFusion 这个执行引擎库之上实现的一个分布式计算框架。虽然 DataFusion 自己有一个子项目 Ballista 某种程度完成了这项任务,但是无论是成熟度还是它为大数据生态做的许多特化设计,都不适合上面这些使用场景的具体需求。

GreptimeDB 的分布式计算框架

  • GreptimeDB 从入口处直接重新实现的整套 PromQL 查询支持。DataFusion 主要支持的是 SQL 查询的解析、优化和执行,但是 GreptimeDB 为兼容 Prometheus 的生态,借助 DataFusion 封装的框架,将 PromQL 当做一种新的方言,通过实现 DataFusion 高度插件化的接口集成到了 DataFusion 的执行引擎当中。

值得一提的是,GreptimeDB 将 promql-parser 完全开源之后,已经成为所有想要实现 Prometheus 接口的 Rust 项目的统一选择。

GreptimeDB 实现了超过 80% 的 PromQL 接口

  • 另一项 GreptimeDB 突出的技术实现,是核心工程师钟镇炽主导开发的一个高度可扩展的索引框架。GreptimeDB 在此之上实现了 MinMax 索引和倒排索引的能力,并将在 0.9 版本里推出针对文本列的全文索引。我们计划将这一框架回馈给 DataFusion 上游,共同演进丰富查询引擎的共享代码。

GreptimeDB 强大的索引框架

  • 演讲之初,我介绍了 GreptimeDB 在时序数据领域上的关键理念创新:时序数据不仅仅是指标(Metrics),同样带有时间戳和上下文信息的数据,例如事件(Events)、日志(Logs)和追踪(Traces),都可以统一在一个时序数据库下进行处理。不仅能够同时优化负载和性能,降低成本,还能通过联合分析获得更加丰富的数据洞察。

统一 MELT 的时序数据处理

  • 最后,我实际超时了大概两分钟介绍了 GreptimeDB 得以快速实现一个高完成度时序数据库所依赖的技术栈:

FADOP Data Infra 技术栈

其中 FAD 都是原先 Apache Arrow 项目群的项目,分别是:

  • Apache Arrow
  • Apache Arrow Flight
  • Apache (Arrow) DataFusion

P 对应的是 Apache Parquet 项目,实际上大量的 Parquet 实现包括 Rust 实现,都包含在 Arrow 项目仓库里。

O 对应的是 Apache OpenDAL 项目。这是由来自 Databend 的工程师漩涡实现的统一数据访问层。接上它,你的项目就可以无痛对接上百个存储后端,包括几乎所有主流云厂商的对象存储服务,都可以用统一的 API 进行读写:用过的都说好。

社群发展

其他讲师大体也介绍了如何应用 DataFusion 的故事,而 DataFusion PMC Chair Andrew Lamb 则站在社群角度分享了 DataFusion 过去一年的进展,包括:

  • 超过 1000 个项目使用 DataFusion 构建自己的软件;
  • 正式从 Apache Arrow 的一个子项目,成为 Apache 软件基金会的顶级项目;
  • 遍布全球的用户会议,包括上面提到的杭州 Meetup 活动
  • 在 SIGMOD 2024 上发布了 DataFusion 的主题论文
  • 全球开发者合作实现的若干项功能,例如查询优化加速、索引加速和内存占用改进等等,其中不少功能是由中国开发者主导或参与实现的。

Andrew Lamb 确实是 10x 工程师

我在今年开始给 DataFusion 做一些实际的贡献,在此过程中深刻感受到了 Andrew Lamb 的热情和生产力。虽然他经常过分乐观的合并别人贡献的代码,导致一些回退(regression)跟接口需要重新适配,但是他 10x 甚至 100x 的生产力能够快速再次解决这些问题。我想这才是在开源社群协同当中真正 distinguished 的能力。正如 Linus 一样,他未必要独立写出 90% 的代码来推动项目前进,而是能每天合并来自全球各地的数十个 pull request 并保证它们相互之间没有实际冲突,也不会导致回退。

对于 DataFusion 这个项目,我的看法与其官方描述一致:

DataFusion is great for building projects such as domain specific query engines, new database platforms and data pipelines, query languages and more. It lets you start quickly from a fully working engine, and then customize those features specific to your use.

它有抽象层级非常高的 SQL 和 DataFrame 接口,能让你直接获得查询计算能力;对于数据库或其他查询系统而言,它也提供了高度可定制化的框架抽象。从纯粹技术理论上说,针对一个特定的数据系统,定制一个查询引擎总是能有最高的优化上限。但是很多时候,这未必是你的系统最核心的竞争力。

因此,就像 GreptimeDB 采用 DataFusion 的历史一样,我们发现这个软件库是一个很好的起点,扩展定制的旅程也很流畅。绝大部分情况下,我们对查询优化的投入程度和生产力是不如 DataFusion 上游的,我们会将这些时间和精力更多放在 GreptimeDB 作为一个统一时序数据库的业务逻辑实现,用户体验优化,以及从云到端的协同方案落地上。或许在未来的某一天,使用 DataFusion 的项目团队拥有足够多的开发者,能够针对自己的系统设计实现一个更加优秀的查询引擎;但是更有可能的是,终其软件一生,DataFusion 都是那个更好的框架和库实现。

改良 SQL Interval 语法:一次开源贡献的经历

作者 tison
2024年7月9日 08:00

本文是 GreptimeDB 首位独立 Committer Eugene Tolbakov 所作。

在上一篇文章《GreptimeDB 首位独立 Committer Eugene Tolbakov 是怎样炼成的?》当中,我从社群维护者的视角介绍了 Eugene 的参与和成长之路。这篇文章是在此之后 Eugene 受到激励,从自己的角度出发,结合最近为 GreptimeDB 改良 SQL Interval 语法的实际经历,分享他对于开源贡献的看法和体验。

以下原文。


开发者参与开源贡献的动机不尽相同。

开源贡献本身是一种利用自身技能和时间,回馈社群和造福更广大受众的方式。开源社群是一个绝佳的环境。参与贡献的人在其中能够与高水平开发者自由交流并建立联系,甚至可能找到可靠的导师或合作者。对于寻求职业发展的开发者,开源贡献可以作为个人技能和经验的展示。当然,也有许多开发者参与贡献只是出于个人的热爱。这很好!当你真正投入到一个开源项目当中后,你可能会发现软件的缺陷或缺失的功能。通过提交补丁修复问题或实现功能,不仅可以消除挫败感,还能让每个使用该软件的用户都受益。

成功参与开源贡献的秘诀不仅仅是编写代码。强烈的学习欲望才是內源动力,由此推动开发者才会主动了解本不熟悉的代码库,并应对其中出现的种种挑战。社群的及时响应和其他资深成员的支持,是这种学习欲望转换为真正参与贡献的重要保障。

社群的及时响应让开源开发者感到宾至如归;其他资深成员提供的指导和反馈,帮助新成员改进自己的贡献水平。理想情况下,你应该可以测试自己的补丁,并将修改版本应用到工作或个人项目当中。这种来自真实世界的需求,尤其是本人的需求,为开源贡献提供了重要的使用场景支撑,确保你的贡献是真的有用,而不是闭门造车的产物。只有如此,通过开源参与做出的贡献才能对整个社群产生持久的影响。

回到我本人的例子上来。虽然我已经花了不少时间锻炼自己的软件开发技能,但事 Rust 对我来说仍然颇有挑战。这种“新手”的感觉可能会让一些人不愿做出贡献,担心他们的代码不够好。然而,我把愚蠢的错误当作提高技能的垫脚石,而不是气馁的理由。

GreptimeDB 社群一年多的参与贡献经历,是一段不断学习并获得丰厚回报的旅程。今天,我会向你介绍其中的一次具体的贡献。让我们亲自动手吧!(或者我应该说,爪子?🦀)

动机和背景

这次贡献主要的目的是支持一个 SQL Interval 字面量的简化语法

1
select interval '1h';

SQL 标准定义了 Interval 的语法:

1
select interval '1 hour';

这个语法相对冗长,我们希望支持上面展示的简化语法,让 select interval '1 hour'select interval '1h' 返回相同的结果。

深入研究代码后,我发现处理转换的核心功能已经存在。为了实现上面的效果,只需针对 Interval 数据类型添加一条新规则:把简化语法格式的 Interval 将自动扩展为标准语法。让我们仔细看看代码中执行相关逻辑的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn visit_expr(&self, expr: &mut Expr) -> ControlFlow<()> {
match expr {
Expr::Interval(interval) => match *interval.value.clone() {
Expr::Value(Value::SingleQuotedString(value)) => {
if let Some(data) = expand_interval_name(&value) {
*expr = create_interval_with_expanded_name(
interval,
single_quoted_string_expr(data),
);
}
}
Expr::BinaryOp { left, op, right } => match *left {
Expr::Value(Value::SingleQuotedString(value))=> {
if let Some(data) = expand_interval_name(&value) {
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op,
right,
});
*expr = create_interval_with_expanded_name(interval, new_value);
}
// ...

代码评审

上面是我第一次尝试写成的代码。GreptimeDB 的资深 Rust 开发者 Dennis 很快发现了改进空间

Dennis 发现了不必要的复制

代码评审是一个优秀的学习渠道。

除了简单地接受建议(因为“减少复制”的理由很明确!),我决定深入研究。通过分析建议并尝试自己解释,我巩固了对 Rust 代码和最佳实践的理解。

避免不必要的 Clone 和所有权转移

起初,我直接在 interval.value 上调用 clone 来取得一个具有所有权的结构:

1
match *interval.value.clone() { ... }

这里的 clone 每次都会创建一个新的数据实例,如果数据结构很大或克隆成本很高,那么这可能是一个性能的影响因子。Dennis 建议通过使用引用来避免这种情况:

1
match &*interval.value { ... }

匹配引用(&*interval.value)避免了 clone 的开销。同样的手法可以用在对二元运算符 left 的匹配逻辑里:

1
match &**left { ... }

这个稍微复杂一些:它使用双重解引用来获取对 Box 内部值的引用。因为我们只需要读取数据结构中的部分信息,而不需要转移所有权,因此只获取引用是可行的。这也减少了 clone 的开销。

清晰的模式匹配

在模式匹配中使用引用可以强调仅读取数据而不是转移所有权的意图:

1
match &*interval.value { ... }

只在需要所有权的时候进行克隆

在第一版代码当中,opright 总是被复制一份:

1
2
3
4
5
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op,
right,
});

但是,其实克隆只需发生在匹配左侧变量是 Expr::ValueSingleQuotedString 变体,同时 expand_interval_name 成功的情况下。Dennis 建议把 clone 调用移动到 if let 块内,从而只在需要所有权的时候进行克隆:

1
2
3
4
5
6
7
if let Some(data) = expand_interval_name(&value) {
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op: op.clone(),
right: right.clone(),
});
// ...

直接引用

在第一版代码当中,我用 expand_interval_name(&value) 显式借用了 value 的值。

然而,valueString 类型的值,它实现了 AsRef<str> 特质,所以它可以被自动解引用成 &str 类型。修改后的版本直接写成 expand_interval_name(value) 不需要再手动 & 取引用。

译注:

这个说的不对。其实是因为 String 实现了 Deref<Target=str> 所以不 clone 以后 &String 可以被自动转成 &str 类型,但是之前 clone 的时候传过去的是带所有权的 String 类型结构,这个时候 &value 取引用而不是把带所有权的结构整个传过去是必要的。

Deref 的魔法可以查看这个页面

总结

在这次贡献当中,代码的“效率”体现在以下三个方面:

  • 避免不必要的克隆,减少运行时开销;
  • 让借用和所有权转移的模式更清晰和安全;
  • 提升整体的可读性和可维护性。

最终版本的 visit_expr 大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn visit_expr(&self, expr: &mut Expr) -> ControlFlow<()> {
match expr {
Expr::Interval(interval) => match &*interval.value {
Expr::Value(Value::SingleQuotedString(value))
| Expr::Value(Value::DoubleQuotedString(value)) => {
if let Some(normalized_name) = normalize_interval_name(value) {
*expr = update_existing_interval_with_value(
interval,
single_quoted_string_expr(normalized_name),
);
}
}
Expr::BinaryOp { left, op, right } => match &**left {
Expr::Value(Value::SingleQuotedString(value))
| Expr::Value(Value::DoubleQuotedString(value)) => {
if let Some(normalized_name) = normalize_interval_name(value) {
let new_expr_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(normalized_name),
op: op.clone(),
right: right.clone(),
});
*expr = update_existing_interval_with_value(interval, new_expr_value);
}
}
_ => {}
},
_ => {}
},
// ...

开源贡献是我找到的加速 Rust 学习的绝佳方式。参与贡献 GreptimeDB 只是一个例子,说明了我如何通过开源贡献获得知识。根据读者反馈,我很高兴在未来的帖子中分享更多这些学习经验!

非常感谢整个 Greptime 团队,特别是 Dennis 提供的帮助和指导,感谢他们在我贡献过程中的支持和指导。让我们继续贡献和学习!

[网盘] 任天堂Switch模拟器 yuzu 下载存档(Windows, Linux)[InfiniCLOUD][腾讯微云]

作者 石樱灯笼
2024年6月12日 16:33

声明:

资源均源自网络,因使用本页面提供的资源链接产生的版权问题或计算机数据安全问题造成的任何损失,本站概不负责。您应该确保在您使用网络资源时已拥有适当抵御病毒的措施和其他安全措施。

本页面含有通向其他网站和资源的链接,这些链接仅供您参考。本站无法控制这些网站或资源的内容,对这些内容或因使用这些内容导致的任何损失或损害,本站亦概不负责。

  • InfiniCLOUD 无墙免注册免登录可以直接下载。
  • 腾讯微云 需要使用 QQ/微信 登录后才能下载。

模拟器资源

请根据文件名自行推断其版本号以及使用方式。

链接顺序与版本号无关。

Windows

yuzu-windows-msvc-1361

来源:Github Relese,已和谐,无从考证

2024-02-27-09-02-46-yuzu-mainline-yuzu-windows-msvc-20240226-da3bc8921.zip

来源:已和谐,无从考证

yuzu-1730.zip

来源:https://yuzu.cn.uptodown.com/windows/download/126081659

yuzu-1734.zip

来源:https://emuyuzu.com/

yuzu-windows-msvc-20240303-7ffac53c9

来源:已和谐,无从考证


Linux

yuzu-mainline-20240303-7ffac53c9.AppImage

来源:已和谐,无从考证

yuzu-1708-1-x86_64.pkg.tar.zst

来源:https://archlinux.pkgs.org/rolling/extra-alucryd-x86_64/yuzu-1708-1-x86_64.pkg.tar.zst.html

yuzu-mainline-git-1734.r0.g5372960-1-x86_64.pkg.tar

来源:https://archlinux.pkgs.org/rolling/chaotic-aur-x86_64/yuzu-mainline-git-1734.r0.g5372960-1-x86_64.pkg.tar.zst.html

yuzu-mainline-20240304-537296095.AppImage

来源:https://emuyuzu.com/


Firmware 固件

固件资源可以去来源页面下载,会有更新的版本

Firmware 15.0.1.zip

来源:https://prodkeys.net/yuzu-firmware/


ProdKeys

ProdKeys资源可以去来源页面下载,会有更新的版本

ProdKeys 15.0.1.zip

来源:https://prodkeys.net/prod-keys/


结尾

不包教包会,yuzu的简易安装说明可以参考我去年的文章:《用我的老电脑玩任天堂Switch模拟器》,只不过 GitHub 的资源已经完全被 DMCA Takedown 了,连 Wayback Machine 的内容都不剩。

百度吃屎去吧。

The post [网盘] 任天堂Switch模拟器 yuzu 下载存档(Windows, Linux)[InfiniCLOUD][腾讯微云] first appeared on 石樱灯笼博客.

分析时序数据:从 InfluxQL 到 SQL 的演变

作者 tison
2024年6月5日 08:00

近年来,时序数据的增长是 Data Infra 领域一个不容忽视的趋势。这主要得益于万物互联带来的自然时序数据增长,以及软件应用上云和自身复杂化后的可观测性需求。前者可以认为是对联网设备的可观测性,而可观测性主要就建构在设备或应用不断上报的指标和日志等时序数据上。

分析时序数据的演变史几乎是大数据分析演变史的复现,即一开始都是把数据存在关系型数据库上,使用 SQL 分析;而后由于规模增长的速度超过传统技术增长,经历了一个折衷技术的歧出;最终,用户在 SQL 强大的理论框架和生态支持的影响下,回到解决了规模问题的 SQL 方案上来。

对于时序数据来说,这一歧出造成了大量时序数据库自创方言。其中以 InfluxDB 在 V1 版本创造了 InfluxQL 方言,在 V2 版本创造了 Flux 方言,又在 V3 里开始主推 SQL 的演变过程最为有趣。

查询语言简介

InfluxQL

InfluxQL 是 InfluxDB V1 自创的查询语言,大体上模仿了 SQL 的结构,以下是一些 InfluxQL 查询的示例:

1
2
3
4
5
SELECT * FROM h2o_feet;
SELECT * FROM h2o_feet LIMIT 5;
SELECT COUNT("water_level") FROM h2o_feet;
SELECT "level description","location","water_level" FROM "h2o_feet";
SELECT *::field FROM "h2o_feet";

InfluxDB 设计开发的年代,实现一个数据库的技术远远没有像今天一样有大量人才掌握。因此,尽管 InfluxQL 努力靠近 SQL 的语法,但是在当时,以关系代数为支撑实现完整的 SQL 查询并添加时序扩展是比较困难的。InfluxQL 转而实现了大量专为时序数据分析设计的功能和运算符。例如,所有查询会默认返回时间列并按升序排序,所有查询必须带有 field 列才会返回结果,面向时间线粒度设计的特殊查询语法,等等。

基本上,InfluxQL 就是 InfluxDB 对以数值指标为主的时序数据分析需求的直接翻译。随着 InfluxDB 产品的发展,InfluxQL 还支持连续查询和指定保留策略,以实现某种程度的实时数据处理。

虽然 InfluxQL 在 InfluxDB V2 中也能使用,但是由于 InfluxDB V2 主推 Flux 查询语言,使用 InfluxQL 会面临一系列模型失配导致的额外挑战

Flux

Flux 是 InfluxDB V2 自创的查询语言。不同于 InfluxQL 模仿 SQL 的语法结构,Flux 的语法应该算作 DataFrame 的流派。Elixir 的开发者大概会对 Flux 的语法感到亲切,以下是 Flux 查询的示例:

1
2
3
4
5
from(bucket: "example-bucket")
|> range(start: -1d)
|> filter(fn: (r) => r._measurement == "example-measurement")
|> mean()
|> yield(name: "_result")

从设计理念上说,Flux 的目的是要支持各种数据源上的时序数据的联合分析。它允许用户从时序数据库(InfluxDB)、关系型数据库(PostgreSQL 或 MySQL),以及 CSV 文件上获取数据,然后进行分析。例如,可以用 sql.fromcsv.from 相关的语法从数据源拉取数据,替代上述示例中 from(bucket) 的部分,后接其他分析算子。

Flux 语言只能在 InfluxDB V2 中使用,V1 上不支持,V3 上被弃用。原因想必大家看完上面这个例子也可以想象:学习成本巨高。更不用说没有专业的语言开发者支持,要在扩展语法的同时修复各种设计实现问题,这几乎是不可负担的工程成本。

SQL

SQL 大家耳熟能详了。它的大名是结构化查询语言(Structured Query Language),理论基础是关系代数。

不同于从业务中生长出来的,专为业务场景定制的方言,SQL 有坚实的理论支持。从 E. F. Codd 发表了经典论文 A Relational Model of Data for
Large Shared Data Banks
之后,五十多年来积累在关系型数据库上的研究汗牛充栋。

尽管各家 SQL 数据库都会实现独特的扩展,有时让用户也挺摸不着头脑,但是在关系代数理论的支持下,基本的查询分析能力,每一个 SQL 数据库都能一致实现。如果在十几二十年前,或许 Data Infra 的舆论场还会出现 SQL 已死或者 NoSQL 才是未来的论调。但是在今天,毫无疑问 SQL 作为数据分析的默认选择已经王者归来。几十年来,SQL 不断地被改进和扩展,并经由一系列久经考验的实现推广,在全球范围内得到了广泛采用。

InfluxDB V3 号称实现了 SQL 查询的支持,并在该版本中推荐用户使用 SQL 分析时序数据。GreptimeDB 在技术选型上和 InfluxDB V3 不谋而合,率先自主实现了面向时序数据的 SQL 数据库,并在多个严肃生产环境当中部署使用。

抛开时序查询扩展不谈,在 GreptimeDB 上可以用标准 SQL 执行查询

1
SELECT idc, AVG(memory_util) FROM system_metrics GROUP BY idc;

SQL 的理论支持帮助新的时序数据库可靠地实现复杂的查询逻辑,以及完成日常数据管理任务。SQL 丰富的生态,也使得新的时序数据库能够快速接入到数据分析的技术栈上。例如,此前制作的输入行为分析示例,就利用 GreptimeDB 支持 MySQL 协议这点,零成本地集成到 Streamlit 上实现了可视化。

时序分析的挑战

SQL

虽然 SQL 有着理论支持强大和分析生态丰富两个核心优势,但是在传统的 SQL 数据库在处理时序数据时仍然会面临一系列的挑战,其中最突出的就是数据规模带来的挑战。

时序数据的价值密度大多数时候非常低。设备上传的信息大部分时候你都不会专门去看,应用上报自己状态健康的数据,也不需要额外留意。因此,存储时间数据的成本效率就至关重要。如何利用新时代的云共享存储降低成本,通过针对时序数据的极致压缩来减少数据本身需要的容量,都是时序数据库需要研究的课题。

此外,如何高效地从大量时序数据中提取关键信息,很多时候确实需要特定的查询扩展来优化。GreptimeDB 支持 RANGE QUERY 以帮助用户分析特定时间窗口下的数据聚合就是一个例子。

Flux

毋庸赘言,学习成本就杀死了这个方言。同样,复述一遍前文的观点,作为一个单一提供商独木难支的方言,其语言本身的健壮性,性能优化能做的投入,以及生态的开发,都面临巨大的挑战,更不用说现在这个唯一提供商还放弃了继续发展 Flux 方言。这下已死勿念了。

InfluxQL

虽然 InfluxQL 查询写起来有些像 SQL 的语法,但是其中细微的区别还是非常让人恼火的。而且,即使努力的 Cosplay SQL 的语法,InfluxQL 从根上还是一个从主要关注指标的时序分析业务需求长出来的方言。它在后续开发和维护成本上的挑战和 Flux 不会有本质的差别。

例如,InfluxQL 不支持 JOIN 查询。虽然你可以写类似 SELECT * FROM "h2o_feet", "h2o_pH" 这样的查询,但是它的含义是分别读出两个 measurement 上的数据(😅):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> SELECT * FROM "h2o_feet","h2o_pH"

name: h2o_feet
--------------
time level description location pH water_level
2015-08-18T00:00:00Z below 3 feet santa_monica 2.064
2015-08-18T00:00:00Z between 6 and 9 feet coyote_creek 8.12
[...]
2015-09-18T21:36:00Z between 3 and 6 feet santa_monica 5.066
2015-09-18T21:42:00Z between 3 and 6 feet santa_monica 4.938

name: h2o_pH
------------
time level description location pH water_level
2015-08-18T00:00:00Z santa_monica 6
2015-08-18T00:00:00Z coyote_creek 7
[...]
2015-09-18T21:36:00Z santa_monica 8
2015-09-18T21:42:00Z santa_monica 7

此外,虽然 InfluxDB V3 在强烈的用户呼声下支持了 InfluxQL 以帮助用户逐步迁移到新版本,但是 InfluxDB V3 主推的还是基于 SQL 的查询方案。换句话说,大胆点判断,InfluxQL 也是一个已死勿念的方言。

注意 InfluxQL 是查询方言,不包括 InfluxDB 行协议(Line Protocol)的部分。InfluxDB 行协议是一个简洁、完整、高效的数据写入接口。它几乎没有再开发和维护的成本,并且通过 Telegraf 的插件生态,能够快速跟一系列数据上报方案做集成。

如何迁移到 SQL 分析

上文提到,InfluxDB V3 仍然支持 InfluxQL 的核心原因是用户需求。诚然,InfluxDB 过去很长一段时间可说是时序数据库的代名词,并且现在仍然是 DB-Engines 上时序数据分类中最高影响力的数据库。因此,不少时序数据分析的用户现有的分析逻辑是用 InfluxQL 写成的。

这里介绍 InfluxQL 跟 SQL 的核心不同,从而说明如何从 InfluxQL 迁移到 SQL 分析。

时间列

应用逻辑迁移当中,最重要的一个区别就是 SQL 对时间列没有特殊的处理,而 InfluxQL 会默认返回时间列,且结果按时间戳升序排列。SQL 查询需要显式指定时间列以在结果集中包含时间戳,也需要手动指定排序逻辑。

数据写入时,InfluxQL 会默认自动用当前时间填充时间列,而 SQL 必须手动指定时间列的值。如果是当前时间,也需要明确写出:

1
2
3
4
-- InfluxQL
INSERT INTO "measurement" (tag, value) VALUES ('my_tag', 42);
-- SQL
INSERT INTO measurement (ts, tag, value) VALUES (NOW(), 'my_tag', 42);

InfluxQL 不支持一个 INSERT 语句插入多列,SQL 数据库通常支持一个 INSERT 语句插入多列:

1
INSERT INTO measurement (ts, tag, value) VALUES (NOW(), 'my_tag_0', 42), (NOW(), 'my_tag_1', 42);

此外,InfluxQL 查询使用 tz() 函数指定查询的时区,而 SQL 通常有其他设定时区的方式。例如,GreptimeDB 支持 MySQLPostgreSQL 设置时区的语法。

时间线

InfluxQL 有一些时间线粒度的查询语法,例如 SLIMITSOFFSET 等。

SLIMIT 会限制结果集中单个时间列返回数据的数量,例如 SLIMIT 1 意味着每个时间列最多返回一个符合过滤条件的结果。

SQL 不是专为时序数据分析设计的,因此需要一些取巧的手段,例如:

1
SELECT DISTINCT ON (host) * FROM monitor ORDER BY host, ts DESC;

这个查询返回以 host 为标签区分的时间列,每个时间列唯一一个结果:

1
2
3
4
5
6
+-----------+---------------------+------+--------+
| host | ts | cpu | memory |
+-----------+---------------------+------+--------+
| 127.0.0.1 | 2022-11-03 03:39:58 | 0.5 | 0.2 |
| 127.0.0.2 | 2022-11-03 03:39:58 | 0.2 | 0.3 |
+-----------+---------------------+------+--------+

通常,时序数据库会实现各自的语法扩展或特殊函数来支持时间列粒度的查询。

时间间隔

InfluxQL 的时间间隔语法形如 1d12m 等,SQL 的时间间隔语法有标准:

1
2
INTERVAL '1 DAY'
INTERVAL '1 YEAR 3 HOURS 20 MINUTES'

数据列和标签列

InfluxQL 从模型上就区分了数据列和标签列,只 SELECT 了标签列的查询是查不出数据的。此外,InfluxQL 支持 ::field::tag 后缀来指定数据列或标签列,并由此支持同名的数据列和标签列。

SQL 标准不区分数据列和标签列,都是普通的一列。不过在具体系统实现上,可能会对概念做一些映射。例如,GreptimeDB 的数据模型就区分了时间列、标签列和数据列,并有对应的映射规则。

GreptimeDB 的数据模型

函数名称

部分函数的名称未必相同。例如,InfluxQL 当中的 MEAN 函数对应 SQL 当中的 AVG 函数。

其他函数,例如 COUNT / SUM / MIN 等等,许多还是相同的。

标识符

InfluxQL 的标识符很多时候需要用双引号括起来,而 SQL 则支持无引号的标识符。

值得注意的是,SQL 的标识符默认是大小写不敏感的,如果需要大小写敏感的标识符,则需要用对应的引号括起来。在 GreptimeDB 当中,默认是用双引号括起。但是在 MySQL 或 PostgreSQL 客户端链接上来的时候,会尊重对应方言的语法。

InfluxQL 标识符引号的部分使用区别示例如下:

InfluxQLSQL
WHERE(“value”) > 42where value_col > 42
GROUP BY “tag”GROUP BY tag_col
SELECT MEAN(“value”) FROM “measurement”SELECT AVG(value_col) FROM measurement

JOIN

InfluxQL 不支持 JOIN 查询,SQL 数据库的一个重要甚至是基础能力就是支持 JOIN 查询:

1
2
3
4
5
6
7
8
-- Select all rows from the system_metrics table and idc_info table where the idc_id matches
SELECT a.* FROM system_metrics a JOIN idc_info b ON a.idc = b.idc_id;

-- Select all rows from the idc_info table and system_metrics table where the idc_id matches, and include null values for idc_info without any matching system_metrics
SELECT a.* FROM idc_info a LEFT JOIN system_metrics b ON a.idc_id = b.idc;

-- Select all rows from the system_metrics table and idc_info table where the idc_id matches, and include null values for idc_info without any matching system_metrics
SELECT b.* FROM system_metrics a RIGHT JOIN idc_info b ON a.idc = b.idc_id;

以上是来自 GreptimeDB JOIN 的示例。目前时序数据库在 JOIN 查询上支持最全的应该是 QuestDB 数据库。

时间范围查询

InfluxQL 的 GROUP BY 语句支持传递一个时间间隔,以按照特定长度的时间窗口来聚合数据。

SQL 没有这样特定的查询能力,最接近的应该是 OVER ... PARTITION BY 的语法,但是这个语法还挺难理解的。

支持 SQL 的时序数据库大多会实现自己的范围查询扩展:

GreptimeDB 的 RANGE QUERY 是其中最强大的。不过其中 ALIGN / RANGE / FILL 的含义和应该出现的位置需要一点点学习成本,我应该近期会写一篇文章来讨论这个场景的需求和 RANGE QUERY 的实现。

持续聚合

InfluxQL 支持持续聚合,这在 SQL 当中是标准的物化视图(Materialized View)的需求,TimescaleDB 就使用了 MATERIALIZED VIEW 的相关语法来实现持续聚合。

不过物化视图在大部分 SQL 数据库中的实现都比较脆弱,目前仍然是一个有待探索的领域。部分时序数据库会实现自己的持续集合方案,例如 GreptimeDB 基于数据流引擎实现了持续聚合

KQIR 查询引擎:Apache Kvrocks 实现 SQL 和 RediSearch 之路

作者 tison
2024年6月3日 08:00

『太长不看版』

Apache Kvrocks 作为 Redis 的开源替代,近期支持了以下查询语法:

欢迎试用或跳转文末到完整示例段落查看具体步骤的含义。

原文作者 twice 发表于 Apache Kvrocks 官方博客。本文是取得原文作者许可的中文译文,翻译过程中间略有措辞顺序调整和演绎。

项目背景

首先介绍一下 Apache Kvrocks 项目:它是 Redis 的开源替代,构建在 RocksDB 之上。因此,就缓存场景而言,Kvrocks 跟 Redis 是有设计上的差距的,但是在数据持久化和数据存储成本方面,Kvrocks 就有设计上的优势。总的来说,Kvrocks 是一个实现了绝大部分 Redis 命令的 NoSQL 数据库。

Kvrocks 支持 RESP 通信协议,包括 v2 和 v3 两个版本。因此,绝大部分 Redis 生态的工具都可以无缝接入到 Kvrocks 上。上面演示的例子就是从 Redis 提供的 redis-cli 命令行工具接入到 Kvrocks 后操作的。

Kvrocks 作为一个使用 C++ 从头重新实现 Redis 的开源替代,不仅支持了 Redis 最基本的命令,还支持了 Redis Stack 当中的高级功能,包括:

Redis Stack 的功能源码一直是商业协议下发布的,新版本的 Redis 也使用了其自制的专有协议发布。Kvrocks 在保证接口行为一致的前提下,完全使用 C++ 从头重新实现,不存在合规风险。本文介绍的 KQIR 更是相同用户体验下完全原创的底层实现方案,与上游实现是相互独立的。

复杂查询的需求

近二十年来,NoSQL 数据库迎来了一段蓬勃发展的黄金时期,一度盖过传统数据库的风头。这主要是因为 NoSQL 数据库性能更好,易于扩展,并且针对特定使用场景有极致的优化。例如,Redis 作为 KV 数据库的代表,MongoDB 作为文档数据库的代表,Apache HBase 作为某种表格数据库的代表,各领风骚数年。

然而,许多用户不愿意仅仅出于性能原因而放弃传统 SQL 数据库的提供基本功能,尤其是 ACID 事务、SQL 固有复杂查询能力,以及结构化数据和关系代数提供的优化和抽象。因此,一批自诩 NewSQL 的新型数据库就此诞生。其中典型包括 TiDB 和 CockroachDB 等。

如前所述,Kvrocks 大体上是一个 NoSQL 数据库。虽然 Kvrocks 算不上 NewSQL 数据库,但是它仍然努力在 NoSQL 和 NewSQL 范式之间取得平衡。在保证 NoSQL 的高性能和灵活性的前提下,Kvrocks 努力实现事务保证和支持更复杂的查询。

RediSearch?

RediSearch 是一个 Redis 模块,它通过查询、辅助索引和全文搜索功能实现了对 Redis 的增强。

虽然其对应的 Redis 命令FT. 开头(即 Full-Text 全文),但它不仅仅是全文搜索。

实际上,Redis 正快速向 SQL 数据库靠拢。RediSearch 允许用户在现有 Redis JSON 或 HASH 数据上创建结构化模式,以用于索引构建。这些模式支持各种字段类型,如数字、标记、地理、文本和矢量,后两者用于全文搜索和矢量搜索。不同于直接支持 SQL 查询的方案,RediSearch 提供了一种独特的查询语法,称为 RediSearch 查询语言。

RediSearch 在许多领域都能找到应用场景,例如利用其矢量搜索功能来支持检索增强生成(RAG)。例如,LangChain 将 Redis 作为其矢量数据库之一。如果 Kvrocks 能够实现 RediSearch 的接口,那么 Kvrocks 就可以作为这些生态当中 Redis 位置的一个潜在选项。对于那些更关注成本和持久化的用户来说,Kvrocks 将成为一个非常有吸引力的选项。

SQL?

RediSearch 自定义了一套语法来进行查询。这产生了一些额外的问题。

首先,RediSearch 的模式(也称为索引,使用 FT.CREATE 创建)可以对应到 SQL 数据库中的一个表。它的查询语法在语义上也与 SQL 查询一致。考虑到这种相似性,支持 SQL 查询并不会带来太多额外工作量。那么,为什么我们不把 SQL 查询也顺带支持上呢?

其次,SQL 的使用范围非常广,为许多人所熟悉,上手更简单。理解 RediSearch 查询语法需要花费相当的时间,而适应新的 SQL 数据库通常更不费力。此外,SQL 为各种查询功能提供了强大的支持,增强了表达能力(例如 JOIN、子查询和聚合等等)。

最后,RediSearch 查询语法受到一些历史设计的影响。例如,AND 和 OR 运算符(在 RediSearch 查询中用空格和 | 运算符表示)的优先级在不同的方言版本中有所不同。这些村规增加了用户的理解成本,而常用的标准 SQL 给到用户的基本假设是相对一致的。

综合考虑,我们认为在 Kvrocks 支持复杂查询时,将 SQL 作为查询语言会是一个不错的决定。

『译注』

自研查询语言很多时候都是死路一条。InfluxDB 自己搞的 Flux 脚本语言已经进入维护模式了,说白了就是死了还没埋。新版本的 InfluxDB V3 一开始只想支持 SQL 作为查询接口,后来迫于存量压力实现了 InfluxQL 的兼容,但是显然 InfluxQL 也是其官方定义下的明日黄花了。

KQIR 的设计与实现

KQIR 的总体架构

为了在 Kvrocks 系统当中支持 SQL 查询,我们需要设计一个健壮的查询引擎。它需要考虑到系统的扩展性、可维护性,以及强大的查询计划和优化能力。

Kvrocks 的方案是 KQIR 框架。在 Kvrocks 的语境下,KQIR 代表着:

  1. 完整的查询引擎,包括语法解析、查询优化和算子执行,等等。
  2. 查询引擎全阶段操作的一种中间表示(IR)。

多层级的 IR

实现 KQIR 的一个主要目的是同时支持 SQL 和 RediSearch 的查询方言。为了屏蔽不同方言下用户输入的差异,我们需要设计一个统一的查询中间表示。

目前,Kvrocks 已经实现了一个支持 MySQL 语法和 RediSearch 查询语法的一个子集的语法解析器。它能够将这两者对应的抽象语法树统统转换为 KQIR 的形式。

KQIR 是一个多层级的中间表示,可以表示优化过程中不同级别的查询结构。抽象语法树首先会转换成 Syantatic IR 的形式,这是某些语法表达式的高级表示。这个形式的 IR 经过优化器处理后,会转变为 Planning IR 的形式。Planning IR 则是一种在查询引擎中表达查询执行计划的低级表示。

此外,我们将在优化之前对 IR 进行语义检查,以确保查询在语义上是正确的。这包括验证它是否不包括任何未定义的模式或字段,并使用适当的字段类型。

IR 优化器

KQIR 优化器由多个阶段(Pass)组成。这仿照了 LLVM 的概念和设计。每个阶段都以某种形式的 IR 作为输入,执行相应的分析和更改,然后生成新的 IR 作为输出。

『译注』

twice 同时也是 LLVM 相关项目的活跃贡献者:

目前,优化器的过程分为三组:

  • 表达式分析:主要优化逻辑表达式,如 AND、OR、NOT 运算符等;
  • 数值分析:通过区间分析优化数值比较,例如消除不必要的比较,或改进比较表达式来实现查询优化;
  • 查询计划生成:把 Syntatical IR 转换成 Planning IR 并通过选择最佳索引以及消除不必要排序来增强查询计划。

Kvrocks 的阶段管理器会控制上述阶段的运行顺序。每个阶段可能运行多次,但最终会收敛并交给执行器执行。

查询计划执行

KQIR 计划执行器是一个 Volcano 模型的实现。

一旦 IR 优化器完成所有优化,计划执行器就可以拿到最终的 Planning IR 结果。然后,计划执行器会将 IR 转化为具体的执行算子,串接成为一个从源端拉取数据,经过层层转换后输出结果的流水线。

随后,Kvrocks 从最终结果的迭代器中轮询拉取数据,取得查询结果。

磁盘上的索引

不同于 Redis 在内存中存储索引数据,Kvrocks 需要在磁盘上构建索引。这意味着,对于任何字段类型,我们都需要设计编码来将索引转换为 RocksDB 上的键值对。

此外,我们需要在执行 JSON 或 HASH 命令前后分别递增地创建索引,以确保查询结果是实时的。

现状与限制

KQIR 功能目前已经合并到 unstable 分支上,支持 FT.CREATEFT.SEARCHFT.SEARCHSQL 等命令。我们鼓励用户进行测试和发布反馈。

然而,KQIR 仍处于早期开发阶段,我们无法保证兼容性,并且,许多功能仍然不完整。因此,即将发布的版本 2.9.0 将不包括 KQIR 组件。我们将在 2.10.0 版本开始发布 KQIR 功能。

字段类型支持

目前,我们只支持两种字段类型:标记(tag)和数字(numeric)。

标记字段用多个 tag 标记了每个数据记录,以便在查询中进行筛选。

数字字段保存双精度浮点范围内的数字数据,允许按特定的数值范围进行排序和过滤。

未来,我们计划扩大支持范围,将向量搜索和全文检索功能与其他字段类型一起实现。

事务保证

目前,KQIR 的事务保证非常弱,这可能会导致使用过程中出现意外问题。

Kvrocks 社群有另一个项目,计划通过建立结构化框架来增强 Kvrocks 的事务保证,从而在 KQIR 实现的 ACID 支持。

『译注』

上述项目也是今年开源之夏(OSPP)的一个项目。

IR 优化器的限制

目前,KQIR 在优化排序时没有使用成本模型,而是依赖一段专门的逻辑。这点会在未来的版本里以高优先级做改进。

此外,KQIR 目前没有使用基于运行时统计数据的优化。我们未来的重点将是将运行时统计信息集成到成本模型中,以实现更精确的索引选择。

与其他功能的关系

KQIR 与命名空间功能集成良好。

FT.CRAETE 创建的任何索引都限制在当前命名空间中,不能在其他命名空间中访问,这与命名空间中访问其他数据的方式一致。

目前,KQIR 无法在集群模式下启用。集群模式支持目前还没有计划,但是这是我们想要实现的功能。欢迎在 Kvrocks 社群当中分享你的需求场景或设计思路。

合规问题

虽然 KQIR 实现了 RediSearch 的接口,但它不包括任何来自 RediSearch 的代码。如前所述,KQIR 采用了一个全新的框架,其查询架构(包括解析、优化、执行)均独立于 RediSearch 的实现。

这点非常重要,因为 RediSearch 并不是开源软件,而是专有许可下的扩展。Kvrocks 的实现保证用户在开源协议下使用相关功能,而无需担心额外的合规风险。这也是 Apache 软件基金会品牌的一个重要保证。

这是一次冒险!

KQIR 目前仍处于早期实验阶段。我们建议用户在生产环境中使用 KQIR 功能时要慎重考虑,因为我们不保证兼容性。但是我们非常欢迎用户试用和提供反馈,这将有助于我们尽快稳定相关功能并正式发布。

未来计划

目前,twice 和 Kvrocks 的其他成员正在快速开发 KQIR 框架。所有上文提到的内容都将继续发展。如果你对这些主题感兴趣,欢迎在 GitHub 上随时了解最新进展。我们欢迎任何期望参与这些工作的开发者加入 Apache Kvrocks 社群并共同创造出有价值的软件。

作为 Apache 软件基金会旗下的开源社群,Kvrocks 社群完全由志愿者组成。我们致力于提供一个开放、包容和供应商中立的环境。

向量搜索

支持向量搜索的设计和实现目前正在进行中。相关进展非常乐观。

Kvrocks 社群的一些成员正在讨论,并提出了在 KQIR 上实现向量搜索的编码设计。

根据计划,我们将首先在磁盘上实现 HNSW 索引,然后引入向量字段类型。

全文检索

目前,Kvrocks 社群还没有全文搜索的设计方案。

不过,我们正在探索通过 CLucenePISA 将全文索引纳入 KQIR 的可能性。

欢迎任何有兴趣参与的开发者分享想法或建议!

SQL 功能

未来,我们计划逐步支持更多 SQL 功能,可能包括子查询、JOIN操作、聚合函数和其他功能。

Kvrocks 的 SQL 能力主要关注的仍然是事务处理,而不是分析任务。

完整示例

首先,我们需要启动一个 Kvrocks 的实例。可以运行下述命令,启动一个 Kvrocks 的 Docker 容器:

1
docker run -it -p 6666:6666 apache/kvrocks:nightly --log-dir stdout

当然,你也可以选择克隆 unstable 分支的最新版本代码,并从源码构建出 Kvrocks 二进制并运行。

成功启动 Kvrocks 实例之后,我们用 redis-cli 工具连接上实例。运行一下命令:

1
FT.CREATE testidx ON JSON PREFIX 1 'test:' SCHEMA a TAG b NUMERIC

这个命令创建了一个名为 testidx 的索引,包括一个名为 a 的 tag 字段和名为 b numeric 字段。

然后,我们可以使用 Redis JSON 命令写入一系列的数据:

1
2
3
JSON.SET test:k1 $ '{"a": "x,y", "b": 11}'
JSON.SET test:k2 $ '{"a": "y,z", "b": 22}'
JSON.SET test:k3 $ '{"a": "x,z", "b": 33}'

写入数据也可以在 FT.CREATE 创建索引之前,执行顺序并不会影响最终效果。

最后,我们就可以用 SQL 语句来基于刚才创建的索引,在这些数据上运行查询了:

1
FT.SEARCHSQL 'select * from testidx where a hastag "z" and b < 30'

除了使用 SQL 查询,RediSearch 语法的查询也是支持的:

1
FT.SEARCH testidx '@a:{z} @b:[-inf (30]'

欢迎下载试用、探索和发表反馈。

Rust 程序库生态合作的例子

作者 tison
2024年5月31日 08:00

近期主要时间都在适应产品市场(Product Marketing)的新角色,不少想法还在酝酿和斟酌当中,于是文章输出没有太多时间来推敲和选题,只能保持每月发布相关的进展或一些零碎的思考。或许我可以恢复最早的模式,多做更新但是文章内容可能不会太过完整。

原本这一期想讨论的是 ASF 开源项目代码的所有权,以及开源软件变更协议的具体含义与操作方式。但是这个话题稍显枯燥,而且要把相关细节讲清楚,还需要继续斟酌。所以我改为采取把最近开始全职投入 Rust 开发,并接触相关生态发展和合作的经历做一点梳理,分享个人在其中的所见所闻。

我会从 Rust HTTP 库的生态切入,从最近一个大事件出发,即 Rust 采用范围最广的 HTTP 库 hyper 和 http 前后发布 1.0 版本,讲述其导致的整个 Rust 应用开发上下游牵一发而动全身的变化。

起因是我在整蛊 GreptimeDB HTTP 相关代码的时候,发现项目依赖的 axum 库是 0.6 版本,而上游是 0.7 版本。这天降的升级闲手,不升有点对不起自己了。通过解决升级过程中的问题,也能帮助摸清 GreptimeDB HTTP 模块的逻辑。

不过,我显然是小看了 Rust 生态荼毒甚广的 ZeroVer 文化的威力。

ZeroVer 是一个揶揄的说法,即在采用语义化版本(SemVer)的前提下,因为各种原因,项目迟迟不愿发布 1.0 版本。

0ver 的魅力时刻

在语义化版本中,0.x 版本是在项目正式发布或说进入稳定期前,一个相对动荡的快速迭代阶段。语义化版本的核心价值是告诉用户升级版本可能面临什么变化:

  • 升级补丁版本(Patch Version):应当只有缺陷修复和性能提升等,不会破坏用户程序的改动。
  • 升级小版本(Minor Version):可能包含新功能,应当向后兼容,用户应用应当可以顺滑升级。
  • 升级大版本(Major Version):可能包含破坏性变更,用户需要做好应对逻辑甚至数据迁移的准备。

当然,在实践当中,语义化版本并不那么严格执行。尤其对于大型项目的实验性功能,是可能有一个独立的可靠机制的。但是无论如何,进入 1.0 之后就意味着项目对用户做了一个向后兼容的保证,除非升级大版本,否则用户会假设软件升级是可以非常激进的。

ZeroVer 方案的反面极端是 Apache Arrow 和 Apache DataFusion 每次发版都升级大版本的做法,很难评价。

话说回来,axum 0.6 到 0.7 的版本升级是一个巨大的变更,基本把核心的类型设计做了一个颠覆,即 Body 不再是泛型了。

这其实也是一个槽点。国内开发者油条哥做的 Poem 就不搞这些花里胡哨的泛型,直接用胖指针抹掉底下的差别,提供更好的开发体验。你说我都 HTTP 了,搞应用层接口开发了,我是跟你抠这点性能的人吗?

一个屏幕都写不完的 breaking changes 列表

Rust HTTP 生态泛型的魅力时刻

于是我着手升级 axum 的版本,一上来就是好几个屏幕的编译错误。没事,Rust 开发者的日常而已。

第一个小问题,我们依赖了 axum-test-helper 这个库,它没跟上 axum 0.7 的版本。我先试着给上游提 PR 升级:

未果。

自己维护比较头疼,其实只有一个文件,最后我在 GreptimeDB 里 vendor 掉了:

这里我又要吐槽了。axum 是不是哪里有问题,居然不提供测试套件,还要下游自己 embedded 然后去掉 (crate) 修饰词,好玩吗?虽然其实也是可以用 reqwest 套一个 TestClient 解决,按照上游的说法:“这只是很薄的一层”,但是这么简单提升使用体验的事情,为何不做呢?

反观 Poem 就提供了 TestClient 工具,开发起来舒服多了。不管是不是我一个文件就能解决,这不是下游应该解决的事情。

紧接着,发现 axum 自己的 TestClient 有落后,以及一个 Rust Nightly toolchain 的兼容问题,提 PR 解决:

Rust Nightly toolchain 的兼容问题是一个很奇妙的问题。因为 Nightly 顾名思义就是最新的 Rust 开发版本,不提供语义化版本保证,只是在 Rust 1.x 的时间线上大体向后兼容。但是结合上 Rust Stabilize 的流程,以及打开 feature gate 如果找不到 feature flag 就会编译失败等等细碎的问题,经常会导致生态在跟进 Nightly 之前有一个无法编译的窗口。

这不算是绝对的坏事,甚至推着生态跟新版本是合理的。但某种程度上其实是 Rust Stabilize 太慢,导致系统开发比如 GreptimeDB 不得不用 Nightly 版本,而出现的新问题。Rust 开发很多用 Nightly 版本,跟 0ver 可能也有某种互相呼应的巧合。在这种环境下,开发公共库并保持多平台多版本兼容,其实是一件非常困难的事情,怪不得都 0ver 了。

然后我就遇到了本次升级最大的大魔王。Rust 生态的 gRPC 库 tonic 闪亮登场!

这里的依赖关系大概是这样的。

首先,axum 0.7 除了接口变化,还有一个关键的依赖变化是把 hyper 和 http 给升到了 1.0 上。因为 Rust HTTP 生态都依赖 hyper 和 http 这两个库,这就导致如果你的接口开始交互,那么所有的结构都要同步到同一个版本。

这个问题并不那么致命。因为如果你合理的 re-export 了依赖的接口,那么同一个 crate 的多个版本是可以共存的,就像我最近在升级 Apache OpenDAL 的时候连带需要升级 reqwest 到 0.12 版本,它依赖了 hyper 1.0 和 http 1.0 但是跟 axum 的 server 端代码关系较小,所以我可以切割开:

注意以上 PR 里修改 use 语句以选择正确的 re-export 符号的变更。

不过,tonic 的情况就比较幽默了。tonic 依赖了 axum 0.6 版本,而且 tonic 和 axum 都用了 tower 作为中间件的第三方库,而且都没有 re-export 而是标榜自己能无缝接入 tower 丰富的中间件生态。

由于 tonic 尚未完成 axum 0.7 和 hyper 1.0 的升级,这下就连环爆炸了:你找不到一个合适的 tower 版本,或者说你找不到一个合适的 axum 版本,来作为 GreptimeDB 被传递关联起来的版本约束。

于是,直到今天,GreptimeDB 的升级还是未完成的状态:

不过,在三月底,tonic 上游就出现了一位大英雄开了一个升级的 PR 完成了主要的工作:

这个 PR 我看到的时候还需要两个 hyper-util 的改动,我给帮忙推着合并了:

这里又岔开一下。虽然 tonic 在 hyperium 组织下,跟 hyper 和 http 一样,但是 hyper 和 http 的作者,Rust 生态真正负责人的英雄 @seanmonstar 并不怎么看 tonic 这个库。tonic 主要是 tokio 的作者 @LucioFranco 和另外两位志愿者 @tottoto 和 @djc 维护的。他们都有自己的本职工作要做,所以并没有太多时间 Review tonic 的变动。实际上,很多项目就用着 tonic 0.11 和 axum 0.6 万年不动也还行的(有没有发现 tonic 也是 ZeroVer 流派)。

不过,经过两个月当中的空闲时间累积,这个 tonic 的升级 PR 终于看着要走进尾声,希望能顺利。开源项目能有这个效率,小几个月跟进一个大型重构,虽然比不上 @seanmonstar 和其他活跃维护项目的响应速度,也绝对算是能及时更新的了。

这个 tonic PR-3610 有很多经典的开源贡献者跟维护者之间的交流和争论,我这里就不展开了。但是我非常建议各位关心开源的人去看看,了解真实的开源世界,未来或许参与进去做出自己的贡献,而不是自己想象或者冷眼旁观。

最后,贴两张图说明 Rust 生态 ZeroVer 的严重程度:

GreptimeDB 的依赖 ZeroVer 有 782 个

GreptimeDB 的依赖非 ZeroVer 有 278 个

在 GreptimeDB 的依赖里,ZeroVer 的占比约莫七成。

其实,GreptimeDB 很多升级都很有工程上的说法,欢迎各位关注发现。我可能也会在今年的某些 Rust 会议上分享相关的经验。

文末放两个我在 Reddit 上跟这次 HTTP 生态大更新相关的讨论链接,在 Rust channel 上还算有一些有趣的讨论。

应当在 ASF 孵化器中帮助项目

作者 tison
2024年4月28日 08:00

由于 Apache 软件基金会(ASF)过去十年在国内的文化传播,许多开源软件的创作者都有一个将自己的软件捐赠到 ASF 并最终成为顶级项目的梦想。我所接触到的 Apache Fury / Apache OpenDAL / Apache StreamPark 都有这样的背景。

按照目前 ASF 的章程和惯例,开源项目要想成为 ASF 顶级项目,绝大多数情况下需要经过 ASF 孵化器孵化。

这个 ASF 孵化器本身是一个 ASF 的顶级项目,与其他项目一样由项目管理委员会指导,通常称为 IPMC 即孵化器(Incubator)项目管理委员会(Project Management Committee)。IPMC 成员规模应当是所有 ASF 项目中最多的,截至今天 IPMC 共有 285 人,项目 Committer 有 3927 人(所有新的孵化项目,孵化期间产生的 Committer 都会自动成为 Incubator Committer)。

本文主要分享我最近在孵化器邮件列表上发布的一封倡议,以及近期帮助孵化项目所做的一些工作。

首先放一下倡议原文,可以从 https://lists.apache.org/thread/h7tblpghtyhlsfolt14jnpylj1cygmxf 公开访问和进行回复。

应当帮助孵化项目的倡议

这封倡议主要是面向 IPMC 成员也就是孵化器导师们的。但是,我认为这样的讨论应该公开进行,因为不涉及安全问题或个人评价,而是孵化器的共识和行为准则。

一言以蔽之,我建议在孵化项目积极回应或解决 IPMC 成员提出的问题或挑战之后,IPMC 成员应当给予积极反馈,认可孵化项目做出的努力,并鼓励项目主动了解和践行 ASF 文化和规则。

一个基本的前提是,在 ASF 的社群模型和定义里,所有成员都是志愿者。这意味着他们不是被雇佣参与开源社群贡献和创造的,也没有上下级关系,而是因志同道合聚集在一起的平等的同侪。

平心而论,大部分孵化器导师不是孵化项目的第一作者,也很少参与到孵化项目的开发贡献当中,甚至对项目的历史和当前社群成员组成都不甚了解。在孵化器导师看来,某个孵化项目出现显而易见的问题,大概率存在其历史原因,或是项目团队其实正在想办法解决。

我对隔着网线能造成的误解深有体会,因此总是尝试假设对方是一个正常人而不是一个弱智,做某件事情肯定有其原因而不是故意破坏,只要能把事情说明白总能取得进展而不是对面就是个恶棍。这些听起来都很平常,但是如果你仔细观察网络空间当中的文字交流,你会发现但凡冲突出现且无法收敛的地方,很多是一方或双方都有前面提到的预设立场。

目前孵化器当中由于沟通能力产生的问题并不鲜见。其中直接触动我的,是一个孵化失败的项目退出孵化器时写下的一段话:

The incubator spends more energy on failing us than helping us.

它说:在孵化过程中,孵化器像是在努力阻止我们成功毕业,而不是帮助我们融入到 ASF 的文化和规则当中。

这很像开发者在一开始 Review PR 的时候,总是执着于找到 PR 当中的错处,把理论上协同开发、合作贡献的流程搞成了大家来找茬。IPMC 成员在审查孵化项目的问题时还有一个额外的问题,就是前面提到的 IPMC 成员往往很少参与项目本身的发展,因此甚至从《大教堂与集市》中提到的 Loken 所有权理论出发,都不那么有权利来评判孵化项目的好坏。

诚然,IPMC 成员大多是经验丰富的老 ASF 玩家。我相信并且从看到的情况看也确实是,每个 IPMC 成员提出挑战的时候,其动机都是为了保证孵化项目符合 ASF 的文化和各种政策要求。但是,我也确实看到 IPMC 成员由于种种原因,草率地给出挑战,或是以一种上级对下级的口吻颐指气使。当挑战的内容本身是个误报,甚至从开发者的角度来说就是无厘头的折腾的时候,IPMC 成员和孵化项目之间的摩擦就会急剧上升。在《全票通过?同侪社群无须整齐划一》里我列举过几个具体的例子。

这些提出挑战的 IPMC 成员往往不是被挑战的孵化项目的导师。因此,他们对这个项目而言更是一个志愿者。我非常尊重这些成员有如此多的时间,来检查大量孵化项目可能存在的问题,而且现在的我也能看清楚他们志愿者的身份,心平气和地按照事情本身的是非曲直来沟通。

不过,对于很多孵化项目来说,遇到直觉上应当经验老到的 IPMC 成员的挑战,还是会很有心里压力。尤其是当孵化项目的导师不能及时处理摩擦(项目导师也是志愿者,时常出现不能及时响应的情况),项目成员很容易感受到孵化器在阻止自己成功,而不是帮助自己更好地成长。

因此,我鼓励 IPMC 成员 follow 其他项目已经广泛验证的经验,在孵化项目做出改进时积极给予肯定,如有可能,自己上手解决发现的问题,或者提供解决问题的具体解决办法。这样,孵化项目能够认识到 IPMC 是帮助自己在孵化器中成长,最终成为 ASF 顶级项目,我相信大部分有志于经历 ASF 孵化的项目,都是有动力配合 IPMC 的指导改进的,只要这些说辞站得住脚。

两个典型的例子。

第一个,@sebb 最近狂盯孵化项目下载页面的合规问题,包括发版投票时候邮件的格式和链接是否指向可靠的地址等等。他提出问题都是非常具体可以解决的,而且在项目解决之后往往会感谢孵化项目及时处理。同时,他还维护了用于 ASF 项目管理的 Whimsy 工具,把这些检查和常见的管理操作尽可能自动化发现和自动化处理。这种从根本上改进流程,又针对每个具体问题提出可以操作的解决方案的做法,为他赢得了相当的声誉。

不过 @sebb 上次跟我扯 ASF 网站模板组织形式的时候,因为被 GitHub UI 显示误导,跟我 battle 了一周,最后发现是他看错了,搞得我也心很累。

第二个,我把自己发现的 StreamPark 距离毕业最大的阻碍,文档的正确、合规和流畅等问题,提出来之后,不仅是 StreamPark 社群的活跃成员积极响应开始改进,还有一位身为专业文字工作者的 IPMC 成员 Andrew Wetmore 伸出援手,仔细改进文档不符合英文表述习惯的地方。这无疑为他赢得了项目成员的尊敬。如果他再对项目的内容提出挑战和改进意见,项目成员也更容易接受。

最后这点我想再强调出来,它代表的是一种同情心或同理心。很多 IPMC 成员对自己辅导的项目都是非常宽容的,经常能够找到通融、网开一面的制度支撑,从根本上是了解体谅项目团队遇到的问题跟当前阶段的困难。相反,当审视其他项目时,往往由于缺乏同情心,容易光是挑战项目的各方各面,而不愿意花时间好好沟通。

比如,孵化器主席 Justin Mclean 在过去几个月里就让我几度血压飙升。

在 HoraeDB 进入孵化器评审的时候,因为原创 CeresDB 团队用新名称捐赠核心代码,Justin 就怀疑人家图谋不轨,搞到最后原来是说代码里很多 CeresDB 的文本还没改过来。如果他一开始就说这些遗留的 CeresDB 文本是问题,马上项目团队就可以开始改,然后我作为 champion 把提案改改,误会就说清了。

在 OpenDAL 进行毕业投票的时候,项目成员没有顺着他的意去处理几个很小的品牌问题,而是就合理性做了一些反问,他就从根本上质疑团队在品牌保护问题上的严谨性,拿放大镜挑各种各样的刺(《Apache OpenDAL 毕业随感》)。最后我找到 ASF 品牌办公室的事务官帮忙评估,才收敛到若干个可实操的改进措施。而即使 OpenDAL 项目成员解决了一系列此前没有细究的品牌问题,在 API Docs 上的品牌合规水平秒杀 90% 的 ASF 项目,Justin 还是不依不挠,沿着预设立场认为你人品有问题。这真是没话说。

在 Fury 刚刚进行的上一轮投票,Justin 稍微表现的正常一点,虽然还是出现了以下经典画面,但是我再三追问之下还是把自己使用的检查工具说出来了,一下子项目成员就能自检出……全都是误报。其中有两个误报只要稍微看一眼就知道是误报,但是 Justin 有功夫回邮件 battle 也不乐意去验证报告的成色。这我只能说是傲慢作祟。至于另外两个,作者能不能多次捐赠代码,Java ArrayAsList Wrapper 这种朴素代码有没有抄袭可能,这搞不清楚只能说是人水平不行。

无言以对

应当说,Justin 自认作为孵化器主席,有责任充当守门员来保证毕业项目质量,同时也会在前几天某个毕业项目出现品牌问题时,被其他人追责孵化期间怎么没有发现,而上火着急自我辩解,肯定还是对 ASF 的角色有认同。而且 Justin 乐意花这么多时间检查这些确实重要的合规问题,客观来说是非常不容易的。

但是他的表达、语气和措辞真的很容易产生摩擦。@sebb 挑战项目的次数跟他不相上下,孵化项目体感上完全不同。而且 Justin 真的特别闲,所以过往孵化器里想争论道理的人,也没有那么多时间跟他吵这些说白了就是官僚主义的东西。加上 Justin 是 ASF 董事会成员,而董事会成员只有他全力关注孵化器,可能再加上姜宁老师和 Craig 偶尔看看,就导致 Justin 实质上成为孵化器里几乎唯一强势的声音。

中国人是最务实的,现在国内项目要进孵化器,第一时间就是找 Justin 来当导师,然后你总不能自己输出自己,把这个“难关”直接自己消解掉。对于我来说,现在我发现我之前也落入到情绪主导思考的坑里,只要自己别着急上火,冷静按照事情本身的是非曲直来讨论,无理的意见就忽略掉,不自己把任何一个社群成员特殊化,沟通还是可以进行下去的。

回到项目孵化的话题上。

虽然 ASF 一直强调社群重于代码(Community Over Code),但是我的理解一直都是活跃的社群才能一直演进代码,死代码不会自己向前走,而不是说“社群优于代码”,或者“社群才重要,代码不重要”。

因为 ASF 的组织目标明确写了:

The Apache Software Foundation (ASF) exists to provide software for the public good.

然后才是:

We believe in the power of community over code, known as The Apache Way.

所以 ASF 存在的意义是向公众提供好的开源软件,Community Over Code 是手段。

而在孵化器当中,IPMC 成员和项目导师往往不怎么参与到软件研发本身去,大多是帮助项目了解 ASF 文化和制度,成为社群形式上合格的 ASF 顶级项目。实际上,项目的价值还是由软件解决的问题本身所定义的。如今 ASF 代码仓库最热门的六个项目,都是充分解决了某个领域的问题,而不是因为它们合规。

所以,作为孵化器导师,我个人做得多的是参与到项目发展中去,这样才能真正站在项目成员的立场上看待问题,而不至于缺少关键评判视角。对于每位 IPMC 成员来说,至少应该对努力创造软件价值的人保持同情心或同理心,认识到他们是 ASF 长期存在且保持影响力的重要贡献者。这样,才能基于平等的同侪对话共同创造价值。

如何处理 Good First Issue

作者 tison
2024年3月15日 08:00

我在《GreptimeDB 社群观察报告》当中提过,GreptimeDB 的 good-first-issue 流转速度极快,大部分容易上手的工作往往在一周甚至两三天内就会有人认领,并且完成的情况也还不错。这个体验很难得。

在最近一些 Good First Issue 的流转过程中,我重新发现了一些典型的模式。正好同大家分享一下我对于如何处理 Good First Issue 这个问题的看法。

还是先看几个案例。

第一个案例算是标杆:

example

example-pr

可以看到这里有两个要点。

  1. Issue 上仔细说明了问题是什么,为什么要解决这个问题,相关代码在哪里,可能的实现思路是什么。
  2. Pull Request 里作者积极的提问,并且说明他做了什么改动,有哪些 alternative 是考虑过但是不可行的。
饱和沟通是开源协同的一个秘诀。

这位 contributor 后来也开始参与到其他 issue 的解决中。

第二个案例介绍了一种处理 stale assign 的方法:

stale

注意这里我并没有直接 assign 给这位新出现的开发者,而是在他提交 PR 之后才 assign 给他表示这个 issue 已经有人开始工作。当然,如果他能在 issue 中表达任意有效信息推动 issue 前进,我也会 assign 给他。

第三个案例是我相对担心的:

concern

因为 issue 的描述我读完以后都觉得,可能需要耗费相当时间梳理清楚相关逻辑。这个时候一个没有过任何开源项目 contribution 经验的新人贸然说要开始接手这个工作,且不说明他的计划,也没有任何问题要追问。在我的记忆里,这种 assignee 很容易回头发现搞不定。如果能主动说自己搞不定,unassign 还好,但是更多人是默默离开,一言不发。

于是我追加问了一下他的计划:

concern-follow

嗯……虽然有点抽象,但是至少多产生了一些有用的信息,暂且信一回。

关于这个问题,我有一段相关的论述:

Assign contributor 的时候可以做个背调,或者直接问一下 TA 的实现计划,除非 issue 确实是闭着眼睛都能做的。另一方面,这也跟 good first issue 有没有相关代码链接,有没有写清楚需求有关。比如上面的典范写清楚了,就容易让 contributor 上手做起来。

Contribution 不只是上来写代码,能经过交流把 issue 往前推进一步,对项目都是有帮助的。因此 issue 也不一定每个都要写得那么详细,毕竟大佬可以自己脑补很多内容。只是很多新手,他不知道可以问问题,或者就模糊地问一句“你有什么可以帮我的吗”。

不用害怕劝退潜在的参与者,如果正常交流都不能回答,大概率不能解决问题,或者做出 PR 来很抽象,review 起来想死。当然,我也见过不用太多交流也能做得很好的,那种大佬会一个 PR 拍你脸上,也不会被劝退。

最近比较像的比如这位:

demo-scene

demo-scene-pr

不过无论如何都有可能出现 assignee 出于各种原因无法完成 issue 的情况。从维护者的角度说,兜底可以问一下进度然后 unassign 掉。这个是可以自动化的。我在 GreptimeDB 上记了一个 Good First Issue 来实现这个自动化:

其他相关的话题这里就不展开了,如果各位有兴趣听,可以回复我来讲。

  • 不同文化背景的 Assignee 的倾向;比如这位是比较典型的。
  • Good First Issue 怎么设置比较合适?
  • 企业开源项目如何在 Issue 上与跨越组织边界合作?

对于关注 GreptimeDB 想找机会参与的读者,现在主仓库还有一些未解决的 Good First Issue 可以上手,即使有人 Assign 了,如果过去数周没有什么进展,也可以由你来接手推进。

contribute

如果你不是 Rust 开发者,GreptimeDB 也有各个语言的客户端正在开发。对于 DevOps 开发者,GreptimeDB 的命令行工具 gtctl 正在准备发布第一个版本,我和项目作者都会及时处理上面的信息。对于前端开发者,GreptimeDB 的 Web UI 也是一个独立的开源项目。

总的来说,GreptimeDB 是一个值得参与的开源项目。其软件产品目标是成为一个高效、实惠,且可以处理分析大规模时序数据的云数据库。目前已经可以替换 InfluxDB 和作为 Prometheus API 的存储后端。

Git 纪元

作者 hutusi
2024年2月25日 23:10
19世纪末,弗雷德里克·泰勒在美国一些工厂推广他的科学管理方法,这包括用科学的方法来代替人工经验判断、对工人进行专业训练以及按专业分工。亨利·福特在他的汽车工厂里也开始应用泰勒科学管理,并且在汽车生产上创新性的采用了流水线作业。1908年,第一辆福特T型车从流水线上面世,而经过优化的流水线在几年后甚至可以达到93分钟内生产一部汽车,强大的生产效率让福特超过了其他所有汽车厂商生产能力的总和。福特的流水线生产很快被其他厂商所借鉴,也迅速普及到了其他工业领域。可以说,福特T型车流水线作业的发明开启了大规模工业生产的时代,人类的生产力得到了巨大的提升。从英国移居到美国的阿道司·赫胥黎对此深受启发,在他的带有科幻色彩的反乌托邦小说《美丽新世界》中,描写未来的人类“文明社会”,人们不再用公元纪年,也不信仰上帝,改尊亨利·福特为唯一的神,以福特为纪元,并以第一辆福特T型车流水线生产的1908年为福特元年。

代码、法律和自由软件

作者 hutusi
2022年10月5日 23:12
劳伦斯·莱斯格是“知识共享”(Creative Commons)的发起人,他本人是位法学教授,同时对互联网颇有研究。因此,在上世纪末互联网如火如荼的时候,他写了一本书《代码》(Code),针对当时网络上流行的互联网无政府主义观点应用法律常识予以反驳,莱斯格的观点很明确:网络不能自治,针对网络空间的规制一定会有,也必须有。

Git 纪元

作者 hutusi
2024年2月25日 23:10
19世纪末,弗雷德里克·泰勒在美国一些工厂推广他的科学管理方法,这包括用科学的方法来代替人工经验判断、对工人进行专业训练以及按专业分工。亨利·福特在他的汽车工厂里也开始应用泰勒科学管理,并且在汽车生产上创新性的采用了流水线作业。1908年,第一辆福特T型车从流水线上面世,而经过优化的流水线在几年后甚至可以达到93分钟内生产一部汽车,强大的生产效率让福特超过了其他所有汽车厂商生产能力的总和。福特的流水线生产很快被其他厂商所借鉴,也迅速普及到了其他工业领域。可以说,福特T型车流水线作业的发明开启了大规模工业生产的时代,人类的生产力得到了巨大的提升。从英国移居到美国的阿道司·赫胥黎对此深受启发,在他的带有科幻色彩的反乌托邦小说《美丽新世界》中,描写未来的人类“文明社会”,人们不再用公元纪年,也不信仰上帝,改尊亨利·福特为唯一的神,以福特为纪元,并以第一辆福特T型车流水线生产的1908年为福特元年。

GreptimeDB 社群观察报告

作者 tison
2024年2月8日 08:00

GreptimeDB 是格睿科技(Greptime)公司研发的一款开源时序数据库,其源代码在 GitHub 平台公开发布。

https://github.com/greptimeteam/greptimedb

我从 2022 年开始知道有 GreptimeDB 这个项目。2023 年,我注意到他们的 Community Program 是有认真写的,不是无脑复制所谓成功项目的大段规则,于是开始跟相关成员探讨开源治理和社群运营的话题。后来,我读过 GreptimeDB 的源代码,发现他们的工程能力很不错,于是就开始参与贡献

经过这几个月的参与,我对 GreptimeDB 的社群有了初步的了解。我认为,这是一个值得参与的拥有巨大潜力的开源社群。于是写作这份社群观察报告做一个简单介绍和畅想。

GreptimeDB 的社群量化情况

两年前,曾有人半开玩笑地说 Rust 和时序数据库都快成开源世界的一个梗了,因为当时有大量的 Rust 语言写作开源项目和定位在时序数据库的开源项目出现。GreptimeDB 也算其中一员,它同样是用 Rust 语言写成的。

不过两年过去,回过头看能够坚持下来不断发展的项目,GreptimeDB 就是为数不多硕果仅存的一员。哪怕跟主流时序数据库项目社群相比,GreptimeDB 的活力也可圈可点。

上面两张图展示了 2023 年,即 GreptimeDB 开源运营第一年,从 OpenDigger 数据集生成的每月 OpenRank 和活跃指数(Activity)数据折线图。

可以看到,从 OpenRank 的维度看,GreptimeDB 显著超越了近年来发展乏力的 InfluxDB 项目,跟 TimescaleDB 和 Prometheus 不分伯仲,相比起战斗民族出品的商业开源产品 VictoriaMetrics 仍有差距。从项目活跃指数的维度看,GreptimeDB 则与 InfluxDB 之外的主流项目同属第一梯队。

OpenRank 是同济大学赵生宇博士定义的一个开源价值流分析指标。相比于容易受先发优势影响的 Star 数和 DB Engines 分数等指标,上面展示的每月 OpenRank 和 Activity 变化情况更能体现出项目当前的发展情况和未来趋势。

GreptimeDB 的社群运营情况

前面提到,我真正开始关注 GreptimeDB 社群的契机是发现他们的 Community Program 并非船货崇拜,而是明显经过思考,有一定可行性的。事实证明,确实如此。2023 年 GreptimeDB 按照 Community Program 的设计发展了两名公司之外的 Committer 新成员:

这两位 Committer 都是通过代码贡献被提名的,提名前都提交了大约二十个代码补丁,且质量被项目团队所认可。此外,这两位 Committer 从开始参与项目发展到成为 Committer 都经过了若干个月的持续投入。可以看到,这个标准下筛选和认可的两位 Committer 在上个月仍然有活跃参与。

应该说,目前 GreptimeDB 的项目功能已经初具规模,能够达到线上交付的标准。这也意味着开荒阶段的大量初创工作已经完成,新加入的社群成员可以在一个坚实的工程基础上发挥自己的创造力。同时,GreptimeDB 在实现优化上还有很大的进步空间,倒排索引、WAL 和存储引擎等技术方向上还有很多未解决的设计实现问题。现在仍然是参与 GreptimeDB 成为 Committer 的机遇期。

不过,GreptimeDB 的 Community Program 距离成为一个商业开源标杆还有不少可以改进的地方。

例如目前邀请新的 Committer 只在 Biweekly 上简单提及。Community Program 设计的结构上并没有即时体现出它正常运转,以及社群存在公司之外的 Committer 的事实。对于活跃参与者和 Committer 的介绍和成功经验分享,也尚有欠缺。目前社群基本处于给代码写得好的人一份权限的朴素运营阶段。

此外,Community Program 虽然已经相比其他船货崇拜的同行删减了许多内容,以保证它能够务实地运作,但是仍然存在一些空洞的组织结构。例如设计出的 Steering Committee 做技术和社群发展决策,但是实际上当前阶段大部分工作就是公司团队商议决定后公开;例如还是定义了 SIG 乃至 OSPO 的组织,但是根本没有人力填充运营这些机构。

我认为,Community Program 应该继续依托当前社群实际运行的状态,结合期望达成且有能力达成的下一个状态,来做修订。例如,提高成为 Committer 的标准和路径的透明性,积极分享案例和邀请 Committer 说出自己的故事。例如,精简冗余和虚假的组织架构的同时,保留在社群征求意见和决策结果向社群公开的关键动作。例如,强调社群成员参与渠道的多样性,鼓励在不同渠道帮助他人使用 GreptimeDB 和参与贡献。这部分是 Ambassador/Advocate 的核心。

除了这个堪称开拓性探索工作的 Community Program 之外,GreptimeDB 社群还有两件事情让我印象深刻。

第一个是 GreptimeDB 社群积极参与开源之夏这样的务实的开源活动,今年释放的三个挑战项目都实现了不错的开源导师传帮带效果:

第二个是 GreptimeDB 的 good-first-time issue 流转速度极快,大部分容易上手的工作往往在一周甚至两三天内就会有人认领,并且完成的情况也还不错。实际认领实现过程中,只要你能够主动更新进展和提问,项目团队成员大多能及时回复。这个体验还是很难得的。

GreptimeDB 的未来发展期望

前面介绍 GreptimeDB 的时候,提到了开源、Rust、分布式、云原生、时序数据库等关键词。虽然这些 buzzword 确实也是 GreptimeDB 能力或特点的一部分,但是从注重实效的程序员(The Pragmatic Programmer)的角度来说,我们还可以做一个具体的展开。

即使当初市面上已经有“恒河沙数”的时序数据库存在,GreptimeDB 的创始团队还是发现了这些现存解决方案没能处理好的问题。对于时序数据当中重要的三个分类:指标(Metrics)、事件(Events)和日志(Logs),大多数时序数据库都只能最优化其中一到两种分类的存储和访问。

GreptimeDB 的创始团队认为,这三类数据可以共用同一套查询层和对象存储层能力,只需要针对各自的数据特性实现各自的存储引擎即可。其中大部分 DB 的架构和能力,例如数据分片、分布式路由,以及查询、索引和压缩等都可以共享。这样,GreptimeDB 最终能够成为同时提供所有时序数据最优化的存储和访问体验的单一系统。

开源应用监控项目 Apache SkyWalking 自研数据库 BanyanDB 也是基于相似的挑战和思考,不过它作为一个监控项目的子项目,更多是以相当特化的方式在实现。但是这反应了时序数据可以统一存储逐渐成为业内共识,所有的通用主流产品都将朝这个方向发展。

在仰望星辰大海的期许之外,GreptimeDB 也有脚踏实地的挑战。

例如,虽然我前面夸赞 GreptimeDB 的工程化水平不错,工程师做功能扩展和代码重构都能找到一个相对整洁的切面,但是软件工程是一个即使知道了原理和最佳实践,真正做出来还是有相当长的必要劳动时间的领域。在快速原型迭代的过程中,GreptimeDB 对内存和抽象的使用是相对奔放的。随着线上应用逐渐增多,GreptimeDB 团队也能收到用户上报的各种性能问题。这就要求重新关注到在快速开发过程里被刻意忽略的细节,精打细算关键路径上的内存使用,针对性能修改抽象以充分利用机器资源。这部分工作都是细致工作,讲究一个 benchmark 发现性能瓶颈并逐个优化。目前的 GreptimeDB 应该有相当多这样的优化机会。

例如,之所以过去时序数据库没能同时服务前面提到的三种不同数据,除了数据建模上的差异,更主要还是因为在数据量暴涨之后,特定数据类型的特定访问形式的读写性能会骤然降低。目前针对此类问题,业界提出了一系列索引方案进行改良。GreptimeDB 目前正在实现其中倒排索引的方案,也将探索结合倒排索引、基于 Cost 的查询优化器和 MPP 查询引擎的自适应方案。这些工作存在许多参与机会,目前 GreptimeDB 团队成员也有不少精力投入在此。

例如,系统层面数据一致性和性能之间的取舍依赖 WAL 模块的实现。目前,GreptimeDB 仅提供了本地的 RaftEngine 实现和 Kafka Remote 的实现,其中 Kafka Remote 的实现发布还不足三个月。这部分工作现在跟进来,参与到现有实现的完善和优化,以及可能的自研 WAL 设计实现过程当中,对任何数据系统开发者而言都将是一段宝贵的经历。

例如,GreptimeDB 在部署形式上支持云端同构部署,时序数据从设备端到云端都是同一套技术栈在处理。这时,如果 GreptimeDB 能够支持一些高级的分析能力,那么时序数据分析的成本将大大降低,体验也将进一步提高。目前,GreptimeDB 已经支持通过 SQL / PromQL / Python 脚本等形式执行分析,正在设计实现基于 Dataflow 技术的分析功能。分析的需求无穷无尽,这一部分对于熟悉数据分析的开发者来说,是一个很好的切入点。

核心数据库系统代码之外,GreptimeDB 还开源了完整的 Dashboard 方案和多语言客户端。再加上本身 GreptimeDB 就支持 SQL 和 PromQL 等业内通用接口,从 GreptimeDB 与生态集成的角度入手参与到 GreptimeDB 的发展,也是一条不错的道路。就在几天之前,我还看到有位同时使用 EMQX 和 GreptimeDB 的开发者向 GreptimeDB 的 Erlang 客户端提交补丁

软件开发参与之外,Greptime 社群维护的两个重要渠道:GitHub Discussions 主题讨论平台Slack 即时通信工作空间都欢迎任何对 Greptime 开源和商业产品感兴趣的人加入。

GreptimeDB 的商业与可持续

我曾经表达过一个观点:商业化不是开源项目可持续的必要条件。因为许多开源软件是个人开发者兴趣所为,这些个人开发者可以有其他经济收入。即使不基于其创造的开源软件做商业变现,也不影响这些开源项目持续维护和发展。

不过,GreptimeDB 是 Greptime 公司研发的开源软件,而公司要想存续下去,就必须以某种形式取得盈利。Greptime 公司投入了不少资本和人力在 GreptimeDB 的研发上,那么 GreptimeDB 总要为 Greptime 的商业成功创造价值。

Greptime 公司目前有两条商业产品线:

  • GreptimeCloud 提供了全托管的云上时序数据库服务,其内核是 GreptimeDB 系统。这个服务可以免费试用,其 Playground 和 Dashboard 做的技术品味都很好。

Greptime Playgorund

Greptime Dashboard

  • GreptimeAI 是为 AI 应用提供可观测性的服务。不同于其他数据库在赶上 AI 浪潮时采用的 PoweredBy AI 增强自身产品的思路,GreptimeAI 是 For AI 增强 AI 产品的思路。其实本轮语言大模型带动的 AI 浪潮对 Database 服务本身的提升还十分有限,反而是这些 AI 应用自身产生的数据需要 Database 来存储和管理。

这两个产品的底层都是 GreptimeDB 的开源代码提供的核心能力,而云控制面、企业管理、安全集成等功能,则是商业代码实现的。

至于为什么要开源 GreptimeDB 数据库核心代码,而不是干脆全部都是私有的商业代码,前几天 Meta 的财报上介绍的 Llama 开源的理由帮我省去了很多口水:

LLaMa 开源的理由

应用在 GreptimeDB 的情况,在 Greptime 团队决心做这个产品的时候,先发的主流时序数据库已经取得极大的优势,且它们几乎全是开源的。这种情况下,就算 GreptimeDB 存在没有历史包袱的优势,直接朝着正确的方向飞奔,但是软件工程的固有复杂度和必要劳动时间并不能无限减少,所以开源是追赶现有主流产品和赢得用户信赖的必选项。

当然,开源软件允许任何用户免费使用,因此构建商业价值不能直接基于开源软件本身。关于 Greptime 如何设计开源模型,或许我会另写一篇文章做对比介绍。目前而言,其开源模型接近 Databricks 的策略。虽然 GreptimeDB 是从头开始写的,不像 Databricks 直接基于开源的 Apache Spark 构造解决方案,但是其核心功能实现重度复用了已有的开源软件:

  • Apache Arrow DataFusion
  • Apache OpenDAL
  • TiKV RaftEngine
  • Apache Kafka

而且,Greptime 团队对于什么功能应该开源是谨慎的,而不是 by default 开源。只有存在这样一个踌躇推敲的过程,才有可能做商业可持续的战略开源。

DISCLAIMER

在社群参与过程中,我跟 GreptimeDB 的核心社群成员有深入的交流,并于近期加入了 Greptime 团队,因此我的观察和评价可能存在一定的主观误差。欢迎各位留言或私信交流意见。

Apache OpenDAL 毕业随感

作者 tison
2024年1月18日 08:00

Apache OpenDAL 简介

Apache OpenDAL 是一个以软件库形式提供的数据访问层。它允许用户通过统一的 API 简单且高效地访问不同存储服务上的数据。你可以把它当作是一个更好的 S3 SDK 实现,也可以通过统一的 OpenDAL API 来简化配置访问不同的数据存储服务的工作(例如 S3 / HDFS / GCS / AliyunOSS 等)。

OpenDAL 以库形式提供,因此使用 OpenDAL 无需部署额外的服务。OpenDAL 的核心代码用 Rust 写成,因此它原生的是一个 Rust 软件库。在项目孵化和成长的过程中,社群也开发出了 Java / Python / Node.js / C 等语言的绑定,以支持在其他语言程序中方便地集成 OpenDAL 的能力。

下图列举了 Apache OpenDAL 多语言实现的线上用户:

real-users

OpenDAL 核心的统一 API 设计,其使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async fn do_business() -> Result<()> {
let mut builder = services::S3::default();
builder.bucket("test");

let op = Operator::new(builder)?
.layer(LoggingLayer::default())
.finish();

// Write Data
op.write("hello.txt", "Hello, World!").await?;
// Read Data
let bytes = op.read("hello.txt").await?;
// Fetch Metadata
let meta = op.stat("hello.txt").await?;
// Delete Data
op.delete("hello.txt").await?;

Ok(())
}

可以看到,实际读写数据的 API 是经过精心设计的。用户想要访问存储在不同服务上的数据,只需修改 Operator 的配置构造,所有实际读写操作的代码都不用改动。

Apache OpenDAL 孵化

OpenDAL 起初是 @Xuanwo 在 DatafuseLabs 为 Databend 项目开发数据访问层时创造的软件库。再往前追溯,Xuanwo 在青云工作时就开发过 BeyondStorage 这一目标相近的软件。不过,由于种种原因,BeyondStorage 最终夭折了

不同于 BeyondStorage 遇到的挫折和经历的歧路,OpenDAL 在一个明确目标的指引下快速成长:

  • 2021 年 12 月,Xuanwo 在 Databend 的代码库中开始开发后来作为 OpenDAL 核心代码的数据访问层逻辑
  • 2021 年 12 月,Xuanwo 同步开始起草 OpenDAL 的定位和目标
  • 2022 年 2 月 14 日情人节,OpenDAL 的核心代码从 Databend 代码库中抽离,开始作为一个独立开源项目运作。

2022 年 8 月,Xuanwo 找到我讨论进入 ASF Incubator 孵化的可能性。彼时项目发展才大半年,几乎所有代码都是 Xuanwo 一个人开发的,也没有 Databend 之外的用户。我发给 Xuanwo 一份 ASF 孵化项目提案的模板并给出项目发展的一些建议,并告诉他如果能在用户增长上做一些工作,主动集成其他知名软件和打造示例场景,过程中发觉合作开发契机招徕开发者,年底应该项目就能成长到进孵化器标准的水平。

今天再次看到 OpenDAL 的提交历史,实际上这个时候 GreptimeDB 应该已经开始调研采用 OpenDAL 的方案。可以看到在 v0.11.0v0.11.4 两个版本的发布里都有 GreptimeDB 的主创人员的参与贡献。

2023 年 1 月,春节前我正好跟 Apache Kvrocks 的成员讨论年后开始准备孵化毕业的事情,想起来之前跟 Xuanwo 交流过 OpenDAL 进入孵化器的意向,于是拉着 Xuanwo 一起起草了孵化提案,并在 2 月初开始孵化讨论

由于项目定位清楚,并且潜在地寄托了替代行将就木的 Apache jclouds 项目的希望,孵化提案顺利地“全票通过”

这段时间的经历可以补充阅读这两篇博文:

接下来的一年,Apache OpenDAL 孵化项目社群高速发展,在功能开发、版本发布和社群成长等各个方面全面取得可观的成绩:

incubation-status

上图所显示的是,孵化期间 OpenDAL 发展了 10 名新 Committer 和 3 名新 PPMC 成员,并有 8 名不同的 Release Manager 发布了 11 个符合 Apache 标准的版本。

上图未显示的是,孵化期间 OpenDAL 发起并实现了 23 个技术提案,处理了超过 1000 个问题报告,合并了 2000 多个代码补丁。

早在 2023 年 8 月,我就判断 OpenDAL 已经接近孵化毕业的标准。2023 年 10 月,在进行过一些沟通之后,项目导师之一吴晟将他发现的毕业前需要完成的工作列了一份清单,正式开始推进毕业工作:

graduation-todos

这份清单上的工作对于 OpenDAL PPMC 来说并不全是简单易懂的,甚至有很多项内容颇有挑战。后面展开讨论 OpenDAL 毕业面临的挑战时,你将看到一些挑战解决起来是很有难度的。于是,在部分简单易懂的工作完成后,清单上的项目有四五周的时间没有任何推进。

2023 年 11 月底,我在准备发起两个新的孵化提案的同时,也决定同时推进 OpenDAL 毕业的工作,免得这些理论上可以一次性完成的工作越拖越久,反而日后需要彻底重新做一遍。

事后,我在跟 Xuanwo 的讨论中得知,这些事务性工作对于开发者来说还是有一定的门槛,看到很多个一眼不知道如何开始的工作项,下意识搁置是第一反应(俗称摆烂)。我作为项目导师,把这些颇具官僚主义色彩的事务性工作用通俗的语言向 PPMC 成员解释,并拆解成一些实际可执行的工作,才能推动项目往毕业的方向前进。

2023 年 12 月,OpenDAL 项目社群内部达成了毕业共识。随后,毕业提案提交到 ASF Incubator 列表上讨论。经过一个月的激烈探讨和继续处理项目存在的问题,2024 年 1 月,项目成功通过毕业投票并在 Board Meeting 上由董事会审核通过:Apache OpenDAL 正式成为 ASF 的顶级项目

PMC Member 的标准

ASF 词汇体系下,PMC Member 即项目管理委员会成员,大致上相当于开源项目的维护者。所有 PMC Member 都是 Committer 并且额外具有对项目发展议题投有效票(binding vote)的权力。

项目在 ASF Incubator 孵化期间也有项目管理委员会,称为 Podling 项目管理委员会(PPMC),其中 Podling 意即孵化中的项目。最初的 PPMC 通常由孵化提案中的 Initial Committers 组成。随后就是上文图中显式的走提名投票流程邀请新的 PPMC 成员。

项目毕业时,毕业提案需要说明最终形成的顶级项目的 PMC 由哪些人组成。通常,原先 PPMC 的成员包括项目导师会加入到顶级项目 PMC 中。此外,孵化阶段邀请的 Committer 也是潜在成为顶级项目 PMC 成员的候选。

万事皆有例外。比如 Apache Doris 毕业时,原先 PPMC 成员部分加入了现在的 StarRocks 公司,并且在孵化期间持续损害 Doris 的品牌。这些成员在毕业时就没有被包含在 PMC 中,甚至不是顶级项目的 Committer 了。

如同我在《Maintainer 的标准》中提到的,我倾向于给予做出贡献的社群成员更高的权限以减少他们参与的门槛,因此在发起 OpenDAL PMC 成员讨论时,我先抛出了一个极端的所有 PPMC 成员和 Committer 都加入 PMC 的提案。

这个提案遭到了 Xuanwo 和其他 PPMC 成员的挑战。他们认为因为“项目毕业”这个契机将 Committers 加入到 PMC 当中,这个说法是行不通的。

随后,项目导师吴晟回应说到 ASF 文化崇尚积极引入 Committers 和项目维护者,Committer 和 PMC Member 在代码权限上是相同的,只是 PMC Member 具有额外关注项目管理的责任,例如处理安全问题、响应 Board 的提问和要求、参与版本发布和投票决议等等。

在后续的讨论中,OpenDAL PPMC 成员表现出把 PMC Member 和 Committer 差别对待,以至于类似等级制度的表述。不过这更多的是一个表达和语境的差异,我在推特上提到

在国内的开源宣传和讨论语境下,确实经常会有一个升级甚至权限交易的 mindset 在。甚至有人就是说开源参与像打游戏一样打怪升级。不同流派和认识存在是很正常的,只是这确实不是 ASF 倡导的方式。

最后,OpenDAL 采取的做法是在包括 PPMC 的成员和项目导师的基础上,询问所有 Committers 参与项目管理成为 PMC Member 的意愿。如果 Committer 都不看邮件列表不回这个邮件,显然跟有意愿参与项目管理事务还是有一定距离的。最终有两位 Committer 回应了轮询,他们也在近期积极参与了项目发展的讨论和组织版本发布。

另一方面,在轮询以外,直到完成毕业流程的两个月间,OpenDAL 也按照以往的流程和标准提名了一位 PPMC 成员。最终毕业成为顶级项目的 Apache OpenDAL 共有 14 名 PMC 成员:初始 4 位,孵化期间提名 3 位,轮询增补 2 位,以及 5 名孵化导师。

从我个人的标准来看,至少愿意花时间做 Release Manager 或是作为项目某个模块的 CODEOWNER 的社群成员,都应该是 PMC 成员。按照这个标准看,PyO3 的核心开发者和 OpenDAL Python 绑定的原始作者 @messense 还不是 PMC 成员,这点应该再 Review 一下。

官方网站和文档

homepage

OpenDAL 的官方网站并不算非常“惊艳”。这一方面是由于核心开发者大多缺乏前端开发技能,另一方面也是作为一个被应用集成的软件库,OpenDAL 不需要独立部署服务,自然也就没有一个独立服务配套的管控页面相关的需求可以展示。大部分情况下,OpenDAL 的使用方式是在软件当中以代码的形式被调用。

在上面的首页信息展示中,可以看到 OpenDAL 主要设计了三个扩展点。

  1. OpenDAL 的核心代码是 Rust 库,但是提供了多语言的绑定,从而可以在诸多语言写成的程序中调用。提供新语言的绑定是一个扩展点。
  2. OpenDAL 的核心价值是屏蔽不同存储服务后端,从而使得用户可以用统一的 API 访问不同位置的数据。提供新的存储后端集成是一个扩展点。
  3. OpenDAL 设计了 Layer 抽象,以在统一 API 的访问链路上提供不同切面的增强,包括重试、日志、监控、超时等功能。

docs

紧接着文档导航页几乎就展示了所有文档内容:OpenDAL 的设计理念,以及跳转到 QuickStart 页面的如何配置四个已经正式发布的语言的软件库。侧边栏的 Services 与其说是文档,不如说是已经支持的部分存储后端的参考手册。

community

相反,关于如何参与 OpenDAL 开发和作为 Committer 或 PMC Member 如何处理事务性工作的文档,由于有实际需要,是相对完整的。

其余页面,博客截至目前只发了四篇,且已经有小半年没有新发布。API 页面除了 Rust 文档,其他语言的 API 文档主要是参考手册性质的。Downloads 和 ASF 相关的页面主要是为了符合 ASF 的要求,对于项目本身基本没什么价值。

项目导师吴晟在毕业自检清单中提到了文档的问题,主要关注的是文档的版本化和避免露出未正式发布的软件库的临时文档。大体上,这是在以 Apache SkyWalking 多语言集成和多模块功能文档的标准给 OpenDAL 提建议。

OpenDAL PMC 成员之一 @suyanhanx 初步完成了文档版本化的调研工作,但是没有彻底完成,也没有更新开发和发布文档以包括相关操作。

我认为,在文档上,OpenDAL 还是有很大的提升空间的。不过在做毕业检查时,我采用了以下的标准:

  • 官网大致能用起来,谈到项目概念时引用链接要齐备。
  • 至少需要让想用 OpenDAL 的人知道如何用起来,整个内容阅读路径是清楚的,就还算可以了。至于版本化的问题,OpenDAL 还没到 1.0 版本,可以先只提供 nightly 版本的文档,这也是目前用户实际的用法。

其实,关于 OpenDAL 的内核设计和各个服务后端的使用方式,Rust 核心实现中已经包括了详细的版本化文档

我认为,OpenDAL 接下来的文档优化方向,除了继续完成所有语言的版本化发布以外,应该注重阐明概念的定义和常见的设计、使用模式,以及不同语言之间的翻译定式。在这一前提下,把实际的文档内容用引用链接导向 Rust 核心实现当中伴随代码的活文档,就可以把存在于 Rust API 文档中的完整文档给利用上。

rust-docs

用户实际的阅读路径,首先从设计、使用模式的文档中确定自己要到 Rust API 文档中查看哪部分模块的具体文档,了解相应的接口契约之后,对应自己使用的语言,查看不同语言直接接口翻译的定式,完成逻辑开发。

如果能做到这个程度,从软件产品角度说,OpenDAL 的产品力才算堪堪能打。

多语言软件库的开发与合规

如前所述,OpenDAL 的一大特色就是在 Rust 核心软件库的基础上,提供了不同语言的绑定,以支持在各种语言写成的程序中利用 OpenDAL 的能力。这也是为什么 OpenDAL 能被寄托替代 Java 写成的 jclouds 库的原因。

目前正式发布的四种语言的 OpenDAL 库的绑定方式如下:

其余开发中的语言绑定包括:

这其中 C Binding 已经有线上用户直接拿去用了,而其他语言的绑定则还未发布或者甚至就只有一个占位符。

在开发多语言绑定的过程中,OpenDAL 总结了一套最佳实践:

  1. 暴力开发出 Hello World 示例;
  2. 重构完成基本的工程化编译和测试流程;
  3. 重构完成基本的 API 映射设计;
  4. 跑通语言对应的发布形式。

这其中最麻烦的其实是工程化的部分和搞清楚最终要怎么发布到目标平台。目前 C Binding 的设计开发已经相对完善,但是由于 C 生态没有一个发布的定式,因此导致了 C Binding 迟迟未能正式发布。

又或者说其实 C 生态就是直接拷贝源文件,所以实际上也已经“发布”。

相反,Rust / Python / Node.js 这样有官方背书的发布平台的语言生态,OpenDAL 可以很轻松的创建对应的 GitHub Actions 工作流来完成自动发布。

值得一提的是,虽然 Java 软件库大多发布到 Maven Central 上,但是 ASF 软件对应的 Repository 不是其他项目常用的 Sonatype 资源库,而是 ASF 自己的资源库。考虑到 Maven 也是一个 ASF 项目,这一点并不奇怪。不过,这就导致支持 OpenDAL Java Binding 自动发布需要 ASF INFRA 介入。OpenDAL Java Binding 是 ASF 第二个支持自动发布的 Java 库,也是第一个自动发布 JNI 原生共享库的 Java 库。相关的工作如下:

此外,OpenDAL PMC 积极与 ASF 的品牌官员合作,探讨在 @apache scope 下发布 OpenDAL NPM 包的方案:

Apache Airflow 的 Jarek Potiuk 正在与 PyPI 团队合作探讨创建一个 ASF 账号的方案。OpenDAL PMC 密切关注进展并随时准备集成 OpenDAL Python Binding 到这个账号下:

可以看到,OpenDAL 认真对待软件发布工作,通过平台提供的机制,以及与 ASF INFRA 密切合作,切实提高了所发布软件包的可靠性。

最后,ASF 在技术合规方面还非常看重所发布的软件的依赖项采用的软件协议是否符合 ASF 对软件协议的政策。OpenDAL 为每个发布的制品都提供了 DEPENDENCIES 文件来披露这一消息。同时,由于大部分其他语言的绑定都是 Rust 核心库的一个翻译层,OpenDAL 开发者们尽可能减少不必要的第三方依赖,以降低下游使用时的合规负担。

技术上,由于需要对接多个存储服务后端,并且存在提供不同语言绑定的愿景,OpenDAL 高度重视代码工程化。

查看 OpenDAL 的 GitHub Actions Workflows 就可以发现,OpenDAL 开发了一套可重用的测试框架,任何新语言绑定或新存储服务后端都能快速具备现有的测试覆盖范围。不过这也不算新奇,同样提供多语言支持和多模块分散开发的 SkyWalking 也研发了适用于自身情况的 SkyWalking Infra E2E 测试框架。

就语言绑定技术而言,Rust 本身支持 C FFI 决定了 C Binding 的实现是非常流畅的。大部分语言也会提供访问 C API 的集成方式,于是通过 C Binding 可以产生其他语言的绑定。这也是 OpenDAL Haskell / Lua / Zig 等一众绑定的实现方式。

在这种大量利用现有技术的方案之外,上面提到的 jni-rs 和 napi-rs 等技术,则是在已有的 C API 集成方式之上,封装了一层符合 Rust 习惯的接口,从而在开发层面只需要涉及 Rust 语言和绑定目标语言。PyO3 更进一步,为这个开发过程研发了一套脚手架,中间打包和配置对接的工作也全部简化了。应该说,这是 Rust 生态主动向绑定目标语言靠拢。底层技术上,两边仍然是基于 C ABI 在通信。

于是这些技术统统可以归类到 FFI 的框架下,跨语言通信的主要成本就产生于数据拷贝和线程模型同步上。可以阅读我的另一篇技术博客《Rust 与 Java 程序的异步接口互操作》了解 OpenDAL 做过的实践。我想 OpenDAL 应该会活跃在 Rust 与其他语言深度集成的前沿。如果生态中有人想要改进 Rust 与某个目标语言的互操作体验,不妨在 OpenDAL 上实践你的想法。

ASF 的政策、官僚主义与基金会发展

上文提到,在毕业讨论进入孵化器邮件列表后,截至顺利毕业前经过了一个月的激烈探讨和继续处理项目存在的问题。

绝大部分毕业前需要处理的问题,其实都包括的项目导师吴晟罗列的清单当中。在处理清单列表的过程中,文档版本问题、依赖合规问题和最终 PMC 人选问题花了一些时间研究讨论,剩下的基本都是按部就班顺利完成。

不过,在清单当中忽略了一个重要问题,那就是 ASF 项目的 PMC 要遵守品牌政策,保护项目品牌和 ASF 的品牌。这其中主要且基础的一条就是以 Apache Xxx 正式名称来引用项目。

OpenDAL 的捐赠没有经历过改名,所以大部分材料和项目核心成员在捐赠过后仍然沿用原来的称呼习惯用 OpenDAL 来指代项目,并认为既然已经捐赠到 ASF 了,那么项目归属 ASF 的事实会随着时间推进被不断强化,因此也没有特别在意。

实际上,ASF 当中明确违反品牌政策的行为主要是 DorisDB 这样直接占用品牌宣传竞争产品,或者在商业公司中用某某项目商业版来称呼自己的产品等等。OpenDAL 虽然脱胎于 DatafuseLabs 公司,但是跟商业化可以说是一点关系也没有。其核心开发者也大多是个人身份参与,所以我认为只要大家没有损害 ASF 品牌的行为也就差不多了。

但是 IPMC Chair Justin Mclean 不这么认为,他在 OpenDAL 毕业提案的讨论里抛出了品牌问题的挑战。

现在回头看,其实一开始 Justin 的表达是 “I found a few minor issues where some name and branding work needs to be done.” 并不十分强烈。但是在 Xuanwo 首次回复没有做到 Justin 期望的完美符合 ASF 政策之后,他表示 PMC 应该要“好好学习相关政策”。

随后,在 PMC 成员完全一头雾水不知道 Justin 所指的到底是什么问题的情况下,项目导师吴晟表达了不同看法,大致跟我上面说的类似,即 OpenDAL 项目成员没有损害 ASF 品牌的动机,实际指出的问题也并不是什么明显的问题,只是没有达到完美主义的标准而已。

这个回复让 Justin 彻底破防,认为 PMC 目无政策,在主观遵守规定的意愿上有严重的缺陷,于是对毕业提案投了 -1 票,并在接下来的一个月时间里充分发挥轴的特性不停地全方位质问。

这个过程给 OpenDAL 项目成员带来了非常不好的体验。不是说不能投 -1 票,而是在主观认定 OpenDAL PMC 不配合、不愿意解决问题之后,连续的挑战都不是奔着具体解决问题去的,而是为了证实 OpenDAL 的项目成员就是一群坏人,且就算 OpenDAL PMC 成员读过品牌政策做过一些 nice to have 的改进以后,得到的也不是认同和进一步改进的建议,而是“你做得还不够好”的持续 -1 批评。

我于是专门写了一篇文章《全票通过?同侪社群无须整齐划一》批评这种行为。实际上,最后 OpenDAL 的毕业提案确实没有“全票通过”

opendal-graduation-result

不过,Justin 本人是 Board Member 之一,哪怕孵化项目在 IPMC 中如上图决策通过,最终是否可以建立顶级项目,还需要 Board 核准。

所以从 Justin 开始挑战,直到最终 Board 一致通过了 OpenDAL 成为顶级项目这一个月间,我跟 ASF 的商标品牌官员 Mark Thomas 以及其他 Board Member 就这个问题进行了全面的讨论。最终我们发现,实际上很多顶级项目都没有严格按照品牌政策来落实自己发布的内容,甚至一些 ASF 基金会层面官方渠道发布的内容,严格按照品牌政策来“审查”,也会有做得不够完美的地方。

不过,这并不是大家可以一起摆烂的原因。相反,这揭示了 ASF 项目在品牌保护上孱弱的现实。我做这些相关内容的讨论,也从来不是为了争个对错,而是带着相关人员重新审视一下目前 ASF 品牌政策的执行情况,从而能够用建设性的目光来评判 OpenDAL PMC 在过去和近一个月来处理品牌政策问题的行为到底做得怎么样。

在 ASF 孵化器里,一般而言顶级项目是不能作为参考的。从务实的角度出发,这是因为很多顶级项目都并不完全合规,也就是这里提到的问题。

但是我仍然坚持应该在孵化器中讨论顶级项目的做法,至少对于做得好的地方应该予以传播和认可。对于做得不好的地方,也不应该局限于孵化器的范畴,而是站在基金会的角度统一协调解决。

这是因为我清楚地知道大多数开源项目进入孵化器,或多或少受到了其他顶级项目的影响,而且进入孵化器明面上的目的就是毕业成为顶级项目。如果顶级项目都在摆烂,都没有按照 ASF 政策行事,孵化项目如何能理解它们被要求符合的政策规定?

探讨的过程中,我们发现了 OpenDAL 和其他顶级项目存在的各种品牌问题,所有已知被发现的问题都被积极解决了。这些工作被总结发送到上面提到的毕业提案结果讨论串上。

Justin 仍然认为 OpenDAL PMC 做得不像他为另一个有志于捐赠到 ASF 孵化的项目做的那样“完美”。但是在我通过对话将他的挑战彻底转化为主观担忧以后,由于我本人问心无愧,说 OpenDAL PMC “不愿意解决问题,只是被动反应,并且习惯性向外甩锅”更是无稽之谈,所以这些挑战在我列举事实的回应之下也就烟消云散了。

在交涉探讨的过程中,我认为有以下几个片段是值得注意的。

第一个是关于孵化器当中指出问题的方式

我用了两组对比。第一个是某个孵化项目对 ASF 执行政策时体现出的官僚主义失望,从而主动退出孵化器时,提到孵化器的维护者们并不是以帮助它们的姿态出现,而是表现得像是要通过项目的失败来证实自己的权威。如前所述,Justin 在 OpenDAL 的案例上最后完全是走向“我对你错”的模式,而不是我如何帮助你一起变得更好。

这未必是 ASF 结构性的问题。对于某个孵化项目来说,主要给予它们这个印象的就是孵化器主席 Justin 本人,有一个“权威”不断地否定你,这种挫败是非常明显的。

第二个是作为一个开源社群,指出问题最好的方式是提交补丁来修复,并在此过程中传达自己的理念,再不济也是提供一个可复现的问题报告,而不是说我认为你有点问题,你要自己发现问题并解决。我用的一个类比,是现在如果有个从未参与过项目开发,也不实际使用项目软件的人,跑过来说我感觉你代码写的方式会出现一些性能问题,你最好自己测一测改过来,这种莫名其妙的报告是无法得到项目维护者的注意的。

这两组对比用英文表达会更加对仗:

  • Helping us rather than failing us
  • Correcting with contributions rather than instructions

第二个是关于政策的文档和实践的问题

ASF 一个做得非常好的地方是它的社群规则和工作方式都有相应的文档记录:

  • 基金会官网 apache.org 记录了基金会的目标,核心定义和各个角色的职责,以及发布、品牌和投票等相关政策。
  • 社群发展网站 community.apache.org 是政策实际执行时的最佳实践参考。
  • 孵化器官网 incubator.apache.org 包括了孵化全过程的指南。
  • 基础设施官网 infra.apache.org 说明了主要资源的位置和使用方式。

但是,这些网站上的内容很是年久失修。

基金会官网的内容东一块西一块,除非很有经验的老成员,否则大多很难快速找到相应的材料。

社群发展网站的最佳实践基本都是十几年前的实践。号称目前开源世界最泛用的成熟度模型,对本次毕业讨论时被挑战的品牌问题只字未提。

孵化器网站内容也是极其杂糅,且有部分“指南”实际是 Justin 个人的偏好,虽然更新时流程上也是经过讨论的,但是大部分审阅的人也很久没有参与孵化,很难对指南落实的时候实际产生的问题有直观的感受。

基础设施官网也是内容稀碎,除非很有经验的老成员,否则大多很难快速找到相应的材料。而且,ASF 在过去二十几年里,基本都在 Maven Central 上发布 Java 代码库,在 SVN 仓库上发布源代码压缩包,对于新时代的不同语言不同软件的发布形式有很大的落差。

Apache OpenDAL 一方面遇上了 Justin 近期跟品牌问题较劲的点子上,另一方面由于它多语言多平台想做自动化发布的工作直接挑战了 ASF INFRA 常年的舒适区,所以在孵化和毕业时相比起其他项目,跟 ASF 的各个机构打交道的次数和时间都要多得多。

这也算是一件好事。毕竟只有新鲜血液的加入,才能促进基金会不断向前发展。只要挑战被正确引导、合作解决,那么遇上问题就不是一件可怕的事情。

第三个是关于基金会本身发展的问题

从另一个角度考虑,为什么 Justin 总是不断地在孵化器当中投 -1 票呢?其实这也反映出孵化器人才梯队建设的问题。由于太多人并不在乎 ASF 政策和 The Apache Way 到底要以什么形式建设出一个什么样的社群和发布什么样的开源软件,所以这些违反政策和文化冲突的问题才会不停挤压到 Justin 这里处理。

久而久之,比起费尽心力去了解项目社群发展的来龙去脉,到底是什么人出于什么动机做了这些事情,人类懒惰地天性就会促使有严格要求的人先直接一个 -1 拍脸,你自己反省。对我来说,我是有足够的动力来处理合规和文化问题的,所以这种处理方式对我来说很冒犯,我会认为原本好好谈合作解决就能行。但是对于某些项目来说,确实你不 -1 我就不管你了,也不是没有这样的案例。

一个更高抽象层次的问题是,ASF 的社群发展和孵化项目的形式,迄今为止仍然是某种“作坊式”的做法。ASF 起源于几个志同道合的开发者聚在一起成立的 Apache Group 并延续了它“一小部分人掌握一系列部落知识运作起一个开源社群”的模式。

在我直接指出作为孵化毕业标准之一的成熟度模型中根本没有关于品牌的论述之前,Justin 宁愿地低效逐个讨论他发现的品牌问题,不断评价 PMC 到底是听话还是不听话,主动还是不主动,也没想到其实这个可以在孵化的必经之路上做提示,最好能做成现在官网基本合规的检查器来提升整个社群的合规水平。

The ASF is well past the point where a small number of folks who have huge “tribal knowledge” can guide the number of projects and podlings that we now have.

我在探讨这些问题的时候,推动和主动修复了一系列文档的缺失,促进了最佳实践的产生和归纳,并且思考到底我们怎么把这些政策、理念和文化传播给更多的人,让他们主动的承担起裂变传播的责任。我想这是 ASF 在走过 25 年之后,面对新的开源社群形势和软件开发方法,应该要考虑和改进的问题。实际上,这也是从参与 ASF 项目接触 Apache 社群理念和方法论的人,成长为基金会成员的一条康庄大道。

全票通过?同侪社群无须整齐划一

作者 tison
2023年12月28日 08:00

近几年,国内开源项目捐赠到 Apache 软件基金会(ASF)的案例很有一些。几乎每个在进入孵化器和从孵化器当中毕业时发通稿的项目,都会选择在标题中加入“全票通过”的字样。

诚然,大部分项目在 ASF 孵化器中茁壮成长,实际上投票结果也是没有反对票,使用这一标题无可非议。然而,对于把同侪社群(Community of Peers)作为社群核心价值之一的 ASF 来说,追求全票通过并不是必须的。

在 ASF 孵化器当中,近些年来由于孵化器主席 Justin Mclean 个人风格的原因,许多项目遭受了无端的审查压力。我认为有必要在国内营造出人人都可以,甚至都应该“全票通过”的氛围时,阐明 ASF 同侪社群的理念和工作方式,以减少项目在面临不合理的挑战时遭受的挫败,尤其是当它来自于某个看起来权威的成员时。

理念与制度支撑

The Apache Way 当中即包括同侪社群的理念:

  • ASF 的参与者是个人,而不是组织。ASF 的扁平结构决定了无论职位如何,角色都是平等的,投票权重相同,贡献是基于志愿的(即使有人因为 Apache 代码的工作而获得报酬)。Apache 社区期望成员之间相互尊重,遵守我们的行为准则。我们欢迎领域专业知识,但不允许有终身仁慈独裁者。

也就是说,ASF 当中所有人在原则上都是平等的,所有的 PMC 成员在投票表决议案时具有相同的权重。

进一步地,ASF 关于投票的专门文档中写到:

  • 对于流程问题的投票,遵循多数原则的常见格式:如果赞成票多于反对票,该问题被认为已通过,而不考虑赞同或反对的具体票数,即反对票不构成否决。
  • 对于代码补丁的投票,反对票构成否决。为了避免否决权被滥用,投票人必须在行使否决权时提供技术理由,没有理由的否决是无效的,没有影响力。
  • 对于版本发布的投票,要通过至少需要三票有效赞同票,且有效赞同票多于有效反对票。反对票不构成否决。

实际操作中,行使技术否决时,如果其他 PMC member 不认同否决者提出的理由,否决也不成立。

因此,全票通过当然是一件值得开心的事情,但是 ASF 的运作方式并不要求需要全票通过。

面对反对意见

我在指导 ASF 孵化项目的过程中遇见过多次反对意见。

先看一个压力没那么大的。StreamPark 的孵化提案在提交表决时,最终是以 8 票有效赞成票,12 票其他赞成票,两票其他反对票通过的。

两票反对票来自 Apache StreamPipes 的项目成员,他们没来由地觉得 StreamPark 跟他们的项目“很像”,所以不应该进入孵化器。

且不说 ASF 并不禁止定位相似的项目进入孵化器,例如复数个消息队列和功能相似的大数据软件,StreamPipes 定位是物联网的工具箱,而 StreamPark 是为流计算系统 Flink 打造的作业管理平台(现在也部分支持管理 Spark 作业)。

所以,这种反对意见,既不是有效票,更没有什么可靠的理由,忽略即可。

再看一个比较搞笑的。Doris 的毕业提案在 2022 年 4 月 27 日以 12 票有效赞成票,13 票其他赞成票“全票通过”。但是孵化器主席 Justin Mclean 在 5 月 15 日找了一下存在感发了一个反对意见

显然,时间已经过去了,而且赞成票远多于反对票,因此 Doris 毕业是既定事实。

面对傲慢的审查

既然是同侪社群,那么允许不同的意见存在就是合理甚至必要的。有人提出反对意见,有人行使投票权投有效反对票,这都是正常的。我在本文开篇所反对的,是通过投反对票带给项目无端压力的傲慢的审查。

上面 Justin 给到 Doris 连续的负面意见,虽然对毕业结果没有影响,但是实际上作为 Doris PMC 整体处理起来的负担并不小。Justin 不停地抛出各种链接,要求 PMC 对此做出解释,其中各种无厘头或者过分的要求层出不穷。

例如,他提到,搜索 Baidu Doris 或者 DorisDB 会出现可能模糊 Apache Doris 品牌的内容,这些内容都需要 Doris PMC 去处理解决。

这根本就是扯淡的。

今天,你主动搜索 Baidu Doris 或者 DorisDB 还是会有各种导向非 Apache 品牌的内容,难道 PMC 整天啥正事儿不干,就陪你做因特网警察?这还是在 Doris PMC 对当时的品牌侵占大户,如今的 StarRocks 有较大影响力,且 Doris PMC 中不少成员受雇投入时间解决这些问题的情况下。

另一个例子来自于几乎全员志愿者的 OpenDAL 项目。

OpenDAL 自进入孵化器以来已近一年,在这段时间里,OpenDAL 提名了 9 位新 Committer 和 3 位新 PPMC 成员,发布了十几个版本,且分别由近十位 Release Manager 主导,不同语言的版本被多个下游软件所依赖。以任何开源社群的标准来看,这都是一个蓬勃发展且做出成绩的项目社群。OpenDAL 的作者 Xuanwo 信任 ASF 的社群发展理念,把 OpenDAL 捐赠到 ASF 当中,其本身就是对 ASF 品牌的认同。

那么好了,在上面链接对应的孵化毕业讨论中,OpenDAL 遭受了怎样的审查呢?

第一次回复,Justin 表示 OpenDAL 的一些引用最好改成 Apache OpenDAL 并带上商标标记,一些第三方的网站提到 OpenDAL 的时候也没有 Apache 的品牌。Xuanwo 看到以后及时的处理,甚至到第三方项目中提交 PR 将 OpenDAL 改成 Apache OpenDAL 的字样。

一般来说,到这里我们就可以认为 OpenDAL PMC 认真对待商标问题,尽力展现 Apache 商标,这已经很足够了。

足够吗?Justin 认为还不够呢。

Justin 进一步提出,按照 ASF 品牌政策的字面意思,所有 OpenDAL 网站的页面,都要用 “Apache OpenDAL” 来指称项目,而且都要带商标名称。最为离谱的是,这个要求连带要执行到 API 文档的每个页面上。

这个真的是保护 ASF 品牌吗?我要打个大大的问号。且不论 OpenDAL 的网站明晃晃的是在 opendal.apache.org 域名下的,根本就没有任何一个 ASF 项目,能够做到在所有网页和材料里都用 Apache ProjectName 指称项目,还要带上商标名称。还是那句话,PMC 整天啥正事儿不干,就陪你搞这些?

说到“任何一个 ASF 项目”,就不得不提 ASF 孵化器讨论里某些人的 360° 立体防御体系。其运作方式如下:

  1. 顶级项目不能作为参考,原因不明反正就是不行。你说某个顶级项目也是如此,他们不会解释为什么顶级项目那么做是有问题的,甚至为什么很多顶级项目都没管这些破事,只会说顶级项目不能作为参考,其回答模式就像低水平 AI 一样。难道孵化器项目毕业,不是为了成为顶级项目?怎么顶级项目反而没这么多破事,到你这就有了?
  2. 其他孵化项目不能作为参考,因为它们反正也没毕业,有问题是正常的。
  3. 基金会以外的项目不能作为参考,因为我们是 ASF 孵化器,别人爱咋咋地。

你发现了吗?这样一套操作下来,一个孵化项目要 argue 自己的做法的时候,不能援引任何其他项目做参考,建设性讨论几乎无法进行。

不能参考其他项目,那怎么界定合理性呢?那就要回到 ASF Policy 及其解释了。

例如,Justin 援引 ASF 品牌政策和自己写的 Incubator Distribution Guideline 说,政策规定项目正式名称是 Apache ProjectName,所以你的 NPM 包名应该是 apache-projectname,PyPI 包名应该是 apache-projectname。下面一众项目发出问号:

哦对了,其他项目不能被引用论述。这下无敌了。

哦,也不一定。比如 Justin 自己要证明说这个包名用 apache- 前缀是合理的时候,他就可以说

This is no different to any project that comes to the ASF via the incubator. Many of them need to change names, often before joining the incubator, and all need to change their name to be in the form “Apache Foo”.

这又可以了。

双标。

当然,没有 ASF Policy 支持,Justin 也可以创造出一些村规来审查你。

例如,Justin 表示 opendal.databend.rs 被重定向到 opendal.apache.org 上,那么 OpenDAL PMC 就要能控制 opendal.databend.rs 这个域名。

哈?所幸 databend.rs 是捐赠 OpenDAL 的企业 DatafuseLabs 控制的,这件事情可能还没那么离谱。换个思路,任何人今天就可以搞定 opendal.vercel.app 重定向到 opendal.apache.org 上,其他服务只要想找肯定能找到,是不是 OpenDAL PMC 还得买下 Vercel 啊?

不过我依稀记得 Justin 自己 mentor 的项目 Answer 也有过 answer.dev 的旧域名吧?这个怎么说呢?

Answer 域名的问题还是我提出来的,我也是 Answer 的导师之一。在这里,Justin 明确说:

redirection would be best

这又可以了。

双标。

再来看另一个莫名其妙的审查。

上面说到要用 Apache OpenDAL™ 来指称项目的事情,OpenDAL PMC 觉得也不无道理,一些显著的引用改改也行的。于是 Python API 文档的首页就用 Apache OpenDAL™ 来指称了:

opendal-python-apidocs

Justin 说这不行,你第一个 opendal 是包名,没有 Apache 字样。所幸我强忍恶心,耐心问了下商标团队的成员这个问题。商标团队的成员是个正常人,曰:“如果工具限制就是这样的,那也没事”。我补了一刀,说你非要说那 PMC 高低得自己做个 API 文档工具来解决合规问题。

当然,只有这个怎么够呢?这首页行了,没说其他页不行啊。pdoc 生成页面是按 Python 模块生成的,Justin 找来一个模块的文档页,指着说:你看,没有 Apache,不行。

opendal-python-apidocs-layer

真要较真,合着以后大家搞网站全别分页了,塞成一个大单页,就像 Kafka 这样

kafka-single-page

合规只要做一次,岂不美哉?哦,Kafka 这个大单页也不符合 Policy 呢。

这种想要做事的人反而莫名其妙多了很多繁文缛节要搞,可不就是官僚主义么?

小结

开源社群存在的首要目的,包括 ASF 自己写的第一愿景,是支持开源开发者生产开源软件。

所谓的政策、指南、规则,其目的应该是保护社群成员免收意外风险侵扰。本身它们是一种非强制性的指引,有道理不遵循也是可以的,更不要说违反了就等同于违法。

我在 ASF 当中得到过很多人的帮助。OpenDAL 作为一个支持多语言的库,为 ASF 在很多发布方面的共识提供了讨论的基础。例如,在我和 Mark Thomas 以及 Drew Foulks 等人的合作下,OpenDAL 搞定了所有 ASF 流程以支持包括 Maven Central 在内多平台自动发布。

Justin 本人愿意花费大量的时间检查孵化项目的发版和提案,我个人对这一点本身是尊敬的。他实际上也指出过很多项目实际存在的合规或品牌问题,而且确实应该被合理的解决,包括上面一开始点出 OpenDAL 的品牌问题,OpenDAL PMC 是有可以改进的地方,也确实改进了。

但是,Justin 把 Policy 苛刻成一种对内进攻项目的武器,用一种非常令人头疼的语气攻击项目,实际上是对 ASF 品牌和孵化器更大的伤害。

此前,这种苛刻又傲慢的审查已经逼退了只有一个核心开发者的 ZipKin 项目:

OpenZipKin 本是监控领域的明星项目,它愿意进入 ASF 并宣传 The Apache Way 是对 ASF 品牌的巨大帮助。然而,在这封令人伤心的退出提案中,ZipKin 的主创 Adrian Cole 无不失望的写到:

Process and policy ambiguity has been ever present and cost us a lot of time and energy.
The incubator spends more energy on failing us than helping us.

这其实是一个早该被提起更高优先级的反馈,ASF 的孵化项目居然感觉到孵化器在促使它们失败而不是帮助它们成功。有了本文前面的介绍,你应该知道这是怎么一回事。

关于流程和政策的争论,我想引用 Rust 作者 Graydon Hoare 的博文 Batten Down Fix Later

这里所谓的不专业或不健康的处理方式,我至少见过四种具体的形式:

  1. 通过花费时间争论消耗对手的精力。

离开阶段常常显得有点突然,因为其原因不透明,并且也有几种不同的形式,通常对应到上述处理的模式:

  1. 由于精疲力竭或倦怠而退出。

毫无疑问,Justin 苛刻且傲慢的审查,就走在这个模式上。

OpenDAL 跟 ZipKin 相似,有一位明确的主创 Xuanwo。如果没有几位导师支持,就像 ZipKin 的 Adrian 一样独自面对这些东西,很难想象如何能够坚持下来。不止一次 OpenDAL PPMC 成员和项目导师对 Justin 的雷人言语表示“麻了”。Justin 本人近五年没怎么正式写过代码也让他的很多“意见”显得非常业余。

例如,要求更改发布平台上 OpenDAL 的 README 包括 Apache 商标,PMC 改完以后说下次发版就会更新。Justin 来了一句能不能不发版就更新 … 你说呢?

当然,如标题所言,ASF 是一个同侪社群,孵化器和基金会并不会因为有一个特别苛刻而傲慢的人就不工作。但是 Justin 是孵化器主席,还是 ASF 董事会九人组的成员,身在基金会中的我即使知道这是同侪社群都会感觉到不可避免的压力,更不用说对此了解较少的其他开发者了。

真要说起来,Justin 的表达真像他自己那样较真的理解,并没有这么大的压力。

例如,在我挑战 NPM 和 PyPI 的包名到底要不要非得用 apache- 前缀后,他改口说 Guideline 都是 SHOULD 不是 MUST 所以有理由的话不用也行。但是又不死心的加了一条临时村规说这个要改也得在毕业提案前,提前跟 IPMC 商量。争论村规毫无意义,但我确实有心情,就说 IPMC 在每次发布的时候都会检查,这些内容都是公开的。毕业提案前,导师组都觉得没问题,怎么你不在导师组里,就得跟 IPMC 商量了?我看你在导师组的项目都不怎么商量啊。

再有一个例子是蚂蚁集团捐赠 CeresDB 核心代码的时候,出于保留商标的商业动机,用新名字 HoraeDB 捐赠核心代码。另一个 ASF 老玩家 Roman 都说这种 Dual Branding 很正常了,Justin 觉得不行。

“Daul branding” is nothing new, but recently, some entities have taken unfair advantage of this (including one you mentioned), and I feel the Incubator should take care that others do not also do this.

诛心言论,死了也证明不了自己只吃一碗粉。我就觉得你未来要 taken unfair advantage of this 了,你说你不是,我觉得你是。

Why a company would be unwilling to give up that brand or trademark just because it may be convenient in the future is a concern.

为什么呢?商业行为,甚至都找不到 ASF Policy 来说这不行了,但我不喜欢,我觉得是个 concern,你就要给我解释。

HoraeDB 的提案,最后我就说不剩什么正经问题了,你的这些意见我都听到了,该说的都说了,我们投票表决。最终 HoraeDB 以 13 票有效赞成票,1 票其他赞成票“全票通过”。Justin 没有投票。蚂蚁集团的运营也好险能继续沿用“全票通过!”的标题。

最后复述一遍,我写这篇文章是为了阐明 ASF 同侪社群的理念和工作方式,以减少项目在面临不合理的挑战时遭受的挫败,尤其是当它来自于某个看起来权威的成员时。

如何撰写一个 ASF 孵化器提案?

作者 tison
2023年12月23日 08:00

随着 Apache 软件基金会(ASF)在国内的深入发展,越来越多的项目希望藉由进入 ASF 孵化器孵化,来建设开源社群。在进入孵化器之前,项目发起人或其核心团队必须撰写一份孵化器提案来介绍项目的基本情况,以供孵化器项目管理委员会(Incubator Project Management Committee, IPMC)评估是否适合孵化。

ASF 孵化器成立于 2002 年,至今已有超过 20 年历史。截至本文写作时,孵化器一共孵化了 347 个项目,其中 241 个项目已毕业,28 个项目正在孵化中。

ASF 孵化器的官网有丰富的文档介绍如何进入孵化器以及按照 The Apache Way 建设开源社群。对于孵化项目而言,最核心的两份文档是:

此外,进入孵化器前需要撰写的提案,孵化器也提供了相应的模板,过往的孵化提案都是公开可查的,这也是撰写孵化提案时的重要参考。ALC Beijing 整理翻译了这份材料,可以阅读其发布文章《ASF 新孵化项目提案指导》

本文从我至今指导孵化五六个项目的经历出发,讨论项目在进入孵化器前撰写提案时经常遇到的问题以及应对方法。

找到一位领路人

提交孵化器提案时,需要一位 IPMC 成员担任该提案的领路人(Champion),其职责主要是作为项目和 ASF 的沟通桥梁,帮助项目进行一些基本的自我评估,并完成孵化提案的评审工作。同样,ALC Beijing 整理翻译了一份材料《Apache 孵化器领路人与导师的职责》做详细介绍。

除了我指导孵化的第一个项目 Kvrocks 是陈亮担任领路人,以及思否捐赠的 Answer 项目是姜宁担任领路人,其他我指导孵化的四个项目都是由我担任这个角色,并帮助项目完成提案撰写和通过孵化器评审。

虽然从流程上说,领路人的职责在项目顺利进入孵化器后就结束,但是许多项目的领路人都会稍后仍然担任项目的导师(Mentor)。毕竟愿意在项目成员还很不了解 ASF 的文化和工作模式的时候,予以指导并帮助项目顺利加入孵化器,需要付出不小的努力。如果不是对项目本身的认同,是很难做这样的投入的。既然如此,那么参与后续孵化,帮助项目进一步建设发展社群,从孵化器中毕业,也就顺理成章。

因此,找到一位领路人不仅是项目进入孵化器时流程上必要的条件,实际上也是找到一位当前在任的 ASF 孵化器导师,愿意支持项目整个孵化过程的发展。

随着 ASF 在国内蓬勃发展,这几年来,进入 ASF 孵化器的源自中国的项目数量可观,我也能看到有许多富有经验的导师和新锐进取的导师在帮助项目孵化。不过,以我现在指导的项目数量来说,我应该很难再支持一个新的项目进入孵化。或许等到现在指导孵化的四五个项目(Kvrocks 已经毕业,OpenDAL 接近毕业)减少到一两个,我还能有多一些时间。国内有作为孵化项目导师意愿的 IPMC 成员我知道一些,但是不合适在这里罗列。如果有想要寻找领路人加入 ASF 孵化的项目,可以跟我联系或者找到 ALC BeijingALC Shenzhen 等组织询问。

第一大原则:真诚

我所指导孵化的六个项目,它们撰写的提案没有第一次就能通过导师自检的。其中最主要的两个问题,一个是英语技能不熟练,另一个就是不够真诚。

原本我想在这写的原则是“诚实”,因为有部分提案编造项目现状尤其是成员多样性和活跃度。这种问题项目导师和后续参与审议的 IPMC 成员稍微查证一下就会暴露,如果在提案正式讨论时才被公开挑战,那么提案通过的概率就非常渺茫了。

不过,“诚实”到底是一个相对客观的标准。很多情况下,项目团队还不至于直接撒谎。然而,如果用“真诚”这样一个相对主观的标准来界定,那么刻意隐瞒、夸大、春秋笔法之类的问题就能囊括其中了。实际评审过程中,不够真诚往往也是很大的挑战。

所谓真诚,意味着明确阐述项目定位,如实披露项目及其社群目前的状况,以及说明加入孵化器的目的和计划。在此之上,知道评议项目提案的人大都是 IPMC 成员,即潜在的孵化器导师,知道 ASF 孵化器的目的是以 The Apache Way 帮助项目发展开源社群,就能够比较好的确定如何写好这个提案。

例如,一个典型的问题是在提案中填充市场材料。这尤其常见于企业团队捐赠的项目,毕竟企业团队在做内部报告和外部宣传时,标配就是一份 PPT 和一系列营销材料。

某项目的孵化提案草案中,几次三番提到捐赠项目的企业建设了中国最大的某品类平台。诚然,首次提及时补充相关信息无可非议,但是原本就不长的的提案里每屏都会出现一到两处提及此事的长难句,属实有点难绷。

某项目的孵化提案草案中,罗列了项目荣获各种开源奖项,希望以此说明项目如何的好。可惜这些开源奖项本身的认可度极低,很多时候是拿着奖项找人领奖。同时其知名度也极低,放在特定环境下或许还有人关注,但是到了 IPMC 成员的审议里,只会认为项目团队是否只是注重虚名。实际上,在提案中需要回答的问题有一条,就是项目需要解释自己的捐赠行为是否只是“对 Apache 品牌的过度迷恋”。

在我的认知中,往往是对社群本身的发展和软件的价值没什么可说的项目,才会期望以填充市场材料的方式来蒙混过关。相反,真正值得展示已有社群规模的项目,通常寥寥数语就能勾勒其形状,IPMC 成员也知道如何求证。

例如,Seata 是阿里巴巴集团和蚂蚁集团共同开发的开源项目,多年以来独立发展得也很好。由于一些原因两家公司希望将 Seata 捐赠到 ASF 当中以在中立的平台和品牌下继续对 Seata 项目做投入。Seata 项目是这样描述其社群的:

Seata is being developed by the development team inside Alibaba who’s responsible for building internal distributed system too. Since Seata was open-sourced on GitHub, it has gained significant traction, receiving up to 24k stars, being forked over 8k times, and having more than 40 versions released. Besides being widely adopted inside Alibaba and Ant Group, Seata is also widely adopted by hundreds of other companies, including … For more information, please click here. We aim to expand the contributor by inviting all those who make valuable contributions and excel in adhering to The Apache Way. The Seata project and its side projects always accept contributions from individuals outside of Alibaba.

因为它确实有人用,所以展示自己的时候才有的放矢。

当然,并非所有项目都像 Seata 一样经过多年的运作。Fury 是蚂蚁集团今年七月份开源的另一个项目,起初项目团队也犯了难,为了让项目看起来勃勃生机万物竞发,把钉钉群的人数也写进去,还把一些未来可能可以做的事情当做已经做完的事情来表述,显得项目似乎得到了广泛的采用。

我首先要求把提及群聊人数的内容给删掉,因为这个大家都知道是怎么回事,背靠大公司或者经过一定的宣传,拉出几个五百人微信群或几千人钉钉群并不是难事,然而其中到底有多少价值,至少不是按群聊人数来衡量的。

此外,我告诉项目团队,Fury 项目的优点是定位清晰,社群用户反馈积极,可以着重突出这一部分。对于未来能做的事情,只要定位说清楚了,再辅以几个确实做过的集成,认真审议的人是能 Get 到的。社群用户反馈积极,则是项目作者杨朝坤在开源的小半年时间里,积极在 Reddit 和 HackerNews 上宣传项目,由于 Fury 作为序列化方案的定位简单易懂,用于替换 Kyro 和 Protobuf 的一些场景下优势明显,所以有不少用户确实公开表示了对项目的兴趣。

最终,我们把 Fury 实际的几个用例做了一点展开来体现项目的价值和其合作的前景:

Currently, Fury has a group of individual users, and organizations such as Alibaba/Vipshop are Fury users, too. Here are some of Fury’s use cases:

  • Ant Ray uses Fury for serialization at 100W+ CPU cores every day at Ant Group.
  • Ant Mars replaced its serialization with Fury, which sped up task scheduling TPS by 2.5X and 4X for data transfer.
  • Ant real-time-graph computing systems replaced Kryo with Fury to speed graph serialization.
  • A few companies replaced Kryo with Fury in Flink jobs for faster data transfer and state persistence.
  • Lindorm at Aliyun uses Fury for serialization between clients and servers.
  • Taobao Android app, which has 880 million monthly active users, is considering using Fury for IPC serialization between Android processes.
  • Vipshop replaced Protobuf with Fury, reducing the end-to-end latency by 30ms.

可以看到,由于开源时间不长,大部分的使用场景仍然在蚂蚁集团内,但这就是实情,而且本身技术场景是多样的。我们没必要编造说因为钉钉群有好几千人,所以或许大概可能这里面对应的几百家企业也在尝试将 Fury 用在生产环境。也许这个逻辑听起来很离谱,但是真的有孵化草案一开始就像这样写的。

这里引出第二个问题,真诚意味着对项目当前的风险如实披露。

例如,Fury 项目开源不久,核心开发者确实只有两个人,而且都是在蚂蚁集团的员工。虽然有过提交的人有几十人,但是显然大家一看 commit 数,时间和内容就清楚怎么回事了。好在前期讨论捐赠的时候,项目引起了 Kvrocks PMC 成员刘明阳(@PragmaTwice)的注意,在提案审议时,项目有三个开发者做出了 non-trivial 的贡献。此外,我为项目找到的另一位导师 PJ Fanning 是 Jackson 的开发者,他也看好 Fury 并改进了 Scala 实现的一些小问题。我也看好 Fury 项目并做了一些微小的改进。最终 Fury 提案是这样表述其开发者团队的:

Currently, Fury has only three core developers, but they are not homogenous: although Chaokun and Weipeng work at the same company, they know each other only due to their common interest in Fury. Mingyang Liu joined the Fury community recently, and he mainly contributed to C++ part of Fury.
We don’t have enough diversity for now. It’s a risk, although we’re optimistic about future developer diversity. Since Fury is open-source, we have attracted more than 20 developers to contribute. We will keep building community diversity following The Apache Way.

在早期商议的时候,其他导师对这一点也提出了担忧。我想到了 OpenDAL 捐赠时也只有 4 个初始成员,且他们都来自 DatafuseLabs 公司,所以我补充了一个评论来自己挑明这个风险和我的判断:

tison’s comment: Although only three initial committers are listed above, PJ (who contributes to Jackson also) and I, as mentors, would participate in the development. Also, another podling that I mentored, named OpenDAL, has four initial committers but so far invited nine (days before its tenth) committers and two PPMC members, done eight (now during its ninth) releases. From my experience with Fury’s initial committers, I saw several shared characteristics with OpenDAL’s members. So, I’d invest efforts to help this project grow within the ASF Incubator.

Hudi 项目到今天也有相当多样的参与者了。然而它起初也只是 Uber 内部的一个方案,在几个合作伙伴间有共享。这就类似蚂蚁集团开发的 Fury 在阿里巴巴集团和唯品会内也有一些采用。Hudi 是这样写关于同质化开发者的风险的:

Currently, the lead developers for Hudi are from Uber. However, we have an active set of early contributors/collaborators from Shopify, DoubleVerify and Vungle, that we hope will increase the diversity going forward. Once again, a primary motivation for incubation is to facilitate this in the Apache way.

这里非常有趣的一句话是,加入 ASF 孵化器的动机之一就是为了发展一个多样的社群。

这体现了撰写孵化器提案的时候另一个常被误解的问题,那就是你并不是来证明说你的项目已经非常好,解决了所有的问题。如果这样,那还要孵化啥呢,直接作为顶级项目运作就好了(这并不是玩笑,实际上已有先例)。或许由于我们总是从一个成功走向另一个成功,我们很少能够坦率的展示项目目前面临的挑战,要么是好的,要么正在往好的方向发展。但是其实 IPMC 并不介意项目有些地方目前还并不跟 ASF 的文化理念和顶级项目的标准对齐,不真诚导致 IPMC 对项目产生信任危机,才是更大的风险。

两个主要段落:现状与风险

我第一次作为孵化项目的领路人,是在 OpenDAL 项目上。当时我误以为孵化提案应该是领路人负责起草的,只是需要项目团队支持补充一些信息。但是,当我实际开始试着写提案的时候,我就发现这个提案的定位就是项目团队的一个自我介绍和自我剖析的。

整个提案大致可以分为两到三个部分:

  1. Abstract 和 Rationale 等前几部分是对项目本身的介绍,项目到底是干嘛的,用在什么场景,相比其他方案有什么差异。这个问题只有核心开发团队才能回答。Initial Goals 是项目捐赠的动机和捐赠后的初步工作,这个也只有核心团队心里才有答案。这部分可以算是简答题。
  2. Current Status 和 Known Risks 也即现状和风险,是几个具体的问题,需要项目披露当前的社群状况和回答一些常见风险的挑战。这部分可以算是问答题。
  3. 此外的内容可以算是填空题,包括源代码的地址,文档的地址,依赖项及其软件协议,捐赠后所需要的资源和初始团队的成员等等。

填空题写起来是比较标准的,简答题除了大规模加塞市场材料突显自己多么 NB 的问题以外通常也不会遇到什么问题,主要出现夸大、掩盖的地方,就是关于现状与风险的问答题。所以我会说它们是一份提案的主要段落。

现状披露分为以下几个方面:

  • Meritocracy
  • Community
  • Core Developers
  • Alignment

Alignment 比较简单,就是说一下跟其他 ASF 项目的联系,没有就不说了,通常没什么问题。是的,提案模板只是一个模板,如果没什么好说的,可以不写。

主要出问题的就是前三项,为了证明社群不只是捐赠方一家在参与,有很多人在用,草案里 Community 的 Core Developers 里凑数夸大的情况非常严重。关于 Meritocracy 就更整蛊了,很多时候项目开源不久或者就是一两家公司的员工在维护,雇佣关系决定提交权限,非要编出多么的开放,甚至为此把一些新来的 Contributor 临时提成 Committer 指着说这就是 Meritocracy 了。

实际上 Meritocracy 并不是多样性的意思,而是任人唯贤,对比的是仅由雇佣关系授予和回收权限的方式。把一个没做什么贡献的人提名成 Committer 反而是违反这一条的。

对 Meritocracy 的回答,其实只要表示出理解它是什么含义就行。当然如果确实已经按照这样在运行了,举出例子是最好的。

风险评估分为以下几个问题:

  • Project Name
  • Orphaned Products
  • Inexperience with Open Source
  • Length of Incubation
  • Homogenous Developers
  • Reliance on Salaried Developers
  • Relationships with Other Apache Products
  • A Excessive Fascination with the Apache Brand

如前所述,下意识的规避项目存在的问题,是不真诚的主要诱因。

Project Name 通常没什么问题,只要做好自检就行。如果要改名或者发现跟其他项目名称冲突,则最好在提案前就完成改名,避免提案对名称产生疑问。Ignite 和 Seatunnel 在捐赠前都改过名字,HoraeDB 也有相似的经历。这里如果没有说清楚改名的缘由,IPMC 评估时发现存在品牌问题,是会有比较大的挑战的。品牌和合规问题是 ASF 当中的红线。

展开说一下,Seatunnel 的原名 Waterdrop 已经被其他组织注册,为了避免品牌问题不得不改名。Ignite 原本是 GridGain 公司的一个项目,其名称 GridGain In-Memory Computing Platform 跟公司名相同,同时作为独立项目也过长。因此实际上 GridGain 可以认为是把 GridGain In-Memory Computing Platform 的核心代码,以 Ignite 的名字捐赠出来。

HoraeDB 情况和 Ignite 类型,是蚂蚁集团的 CeresDB 产品核心代码,用 HoraeDB 的名字捐赠给 ASF 孵化器。然而,由于技术上需要修改代码中对 ceres 文本做替换,我在指导项目撰写提案的时候使用了 rename 一次来表达这个操作。实际评议提案时,技术上的名称替换做了一小部分,rename 的用词导致某些 IPMC 成员对 CeresDB 和 HoraeDB 的关系产生了误会,由此引发了不必要的一系列争论。

Orphaned Products 主要可能的风险有两种,一个是个人开发者捐赠的项目,如果人数很少,一旦开发者转移兴趣,项目可能就死了。另一个是公司捐赠的项目,如果没有 solid 的用例,可能公司一转向,项目也就死了。

如果项目开发者足够多样,有很多用户从中获益,这自然没问题。但是如果人并不多,IPMC 就需要评估项目的定位和未来发展的可能性。例如 Fury 未来长出一个社群的风险并不高,替换逻辑也清晰,不会因为项目没有用而废弃。相反的例子是 Tuweni 作为 Java 作成的区块链工具,虽然也进入了孵化,但是很快就因为没什么人感兴趣,作者自己也觉得没指望而放弃。

如果是公司项目,很多时候公司项目总要打包票说未来可期大大的投入,所以在 IPMC 这关通常也不好挑战。但是对于导师们来说,就要评估这个项目到底有没有足够的高层支持和内外用例。因为公司改主意经常比个人开发者转移兴趣或 burnout 来的快,除非有什么外力牵引,否则项目组换方向或者原地解散,这项目就死定了。对于导师来说,投入帮助项目孵化和成长的努力也就打了水漂。

类似的例子比如 Hotonworks 的 Ambari 项目,在公司被 Cloudera 收购后,项目跟 CDH 冲突,也就停摆了。但是换个角度看,由于 Ambari 仍然有其价值,所以在归档一年后,又有人出来复活这个项目继续运作。可惜的是由于一些其他原因,这次复活后的运作也不顺利。

另一个例子是同样来自 Hotonworks 的 Livy 项目,由于类似的原因停摆。然而在其从孵化器中退休前,有的用户已经上船并在生产环境使用,所以他们自告奋勇来维护这个项目,至少保持能够修复 BUG 和安全漏洞的情况。

Inexperience with Open Source 这个也是经常容易不真诚的地方。

从我的经验来看,大部分想要进入孵化器的项目,往往在社群建设上还有很大的欠缺。如果一个项目已经建立起了完善的社群,例如 ApolloConfig 和 Nacos 这样的,它们很少会想到要捐赠给 ASF 谋求进一步发展。因此,如实披露项目成员在开源方面的经验即可。

反面例子是一些草案在披露是添油加醋故意夸大,说是有某个成员四五年前在某个知名不知名的开源项目里提交过补丁,这就算是不会 Inexperience with Open Source 了。这就像是在建立上放一个 fix typo 的 PR 链接,然后说自己深入参与开源项目开发一样。

Length of Incubation 标准答案是 2 个月进入孵化器,2 年内孵化毕业:

Expect to enter incubation in two months and graduate in about two years.

这个在提案时基本不会被挑战,不过背后是孵化器运作的部分逻辑,即孵化项目是要奔着毕业去的,如果长时间不能毕业,可能会面临被要求退休的风险。历史上退休的孵化项目一共有 78 个,除了项目自己放弃以外,IPMC 会定期评估孵化时间过长的项目,并需要项目对风险做出回应。如果没有回应或确实状况很不健康,可能由 IPMC 出面将其退休。

Homogenous Developers 同质化开发者,即项目核心团队的多样性。这个在前面讨论真诚的时候已经介绍过很多。人的兴趣会转移,精力会消磨,公司的注意力转移得更快。如果项目本身不能凭借自己的价值吸引到足够多样的开发者,那么其失败的风险是极高的。

Reliance on Salaried Developers 这点跟同质化开发者有相似之处,不过其实并不一定是坏事。Flink 的开发如果不是拿钱,一个纯粹的志愿者是很难撑起社群的需要的。所以关于这个问题,我认为要么是不依赖,表达出项目核心团队对技术本身的追求和认同,要么是确实就是一直有钱雇佣开发者做这个项目,都没有问题。同样这里容易产生一些不真诚的地方,明明就是一群拿钱办事的人,编造出自己没钱也会做的谎言就显得很可笑。

HoraeDB 的提案里并没有回避这个问题:

We acknowledge that most developers are supported by their employers to contribute to HoraeDB, which poses a significant risk. However, HoraeDB has already been extensively deployed within Ant Group, with no internal forked versions. The version available on GitHub is the actual production version used in practice. As a result, Ant Group can ensure long-term commitment. We believe that within this timeframe, we can attract more maintainers and developers from diverse backgrounds to address this risk.

作为对比,Fury 的作者独立开发这个项目两年多,他有底气说自己就是会做这项技术,而不只是因为被雇佣做这件事:

Although Fury is created at work time in Ant Group, Chaokun and Weipeng contribute to Fury in their spare time. They love the process of building such a versatile framework and the value it brings to all users and organizations. They will continue to work on Fury even if they leave their current cooperation, and Mingyang Liu also contributes to Fury in his spare time. We plan to attract more committers to address this risk.

Relationships with Other Apache Products 如实回复即可。

A Excessive Fascination with the Apache Brand 这个问题是整个提案模板里最容易被误会的一条,反应了我一开始说的撰写提案时最主要的两个问题,第一个是英语技能不熟练。

这个问题的意思是,项目是否只是“对 Apache 品牌的过度迷恋”而捐赠,而不是孵化器关注的按照 The Apache Way 建设社群。换句话说,是不是只想借 Apache 的品牌做营销。不少草案写作时不知为何理解成要表达对 Apache 品牌的认可,洋洋洒洒写了一堆说 Apache 品牌是如何如何的好,完全是背道而驰。

当然,你说想要捐赠到 ASF 的项目团队不认同 Apache 品牌,这也不可能。可以参考 Kvrocks 提案的写法:

Although we expect that the Apache brand may help attract more contributors, our interest in starting this project is based on the factors mentioned in the fundamentals section. We are interested in joining ASF to increase our connections in the open-source world. Based on extensive collaboration, it is possible to build a community of developers and committers that live longer than the founder.

其他琐碎的问题

第一个,前面已经说过,这里再提出来强调,孵化器提供的模板只是个模板,不一定要完全按照它的格式来写,不知道写啥的可以留空或者询问领路人。

第二个,填空题的标准答案可以看 Fury 提案。一般来说需要三个邮件列表:

  • private 用于讨论不适合公开的内容,例如安全问题的处理,涉及具体人的讨论,例如提名新 Committer 或某人行为不端等。
  • dev 用于项目开发的讨论。以往还会有一个 users 来回答用户问题,但是现在大部分用户习惯开 Issue 或者 Discussion 聊,除非有明确需求,否则复用 dev 已经足够。
  • commits 用于归档项目开发产生的信息。这个主要是 The Apache Way 约定所有开发活动都要在邮件列表上留痕,ASF 会维护邮件服务器和保存所有邮件,这保证 ASF 项目的所有信息资产不会依赖外部系统。

Git 仓库现在基本都是 GitHub 上的迁移,不过 ASF 实际上有自己的 GitBox 做同步来确保不依赖外部系统。

Issue Tracker 大部分新项目都用了 GitHub 的 Issue 功能,老项目很多还使用 ASF 自建的 JIRA 平台。同样这些活动会被同步到邮件列表上,确保信息保存不依赖外部系统。

Initial Committers 的人选要仔细,这个也是经常被挑战的问题之一。在这个问题上,倒是可以多考虑 Meritocracy 的问题,同时不要设置过高的门槛但也不要为了凑人数而把没有什么实质性参与的 Contributor 强行提上来。最后,小心不要把没有参加项目实际工作的公司老板安排进来,这个如果被公开挑战会很难回应,因为跟 ASF 的文化完全格格不入。

Sponsoring Entity 捐赠给孵化器就写孵化器,Sponsors 需要注意领路人(Champion)只负责项目进入孵化器的过程,所以如果他想要继续担任项目导师,在导师一栏里要重复提名。

最后一个,依赖项的协议要认真看,从别处抄来的源代码要保留原先的文件协议头信息。合规和品牌问题是 ASF 的红线,IPMC 里有些人会特别仔细的看这些问题的。关于 ASF 项目如何看待依赖项协议是否合规,可以参考这份材料

注意有不符合 ASF 合规要求的依赖,只要写清楚了而且有后续修复方案,并不需要在进入孵化器前就一定解决。ASF 对孵化项目的 endorsement 其实是有限的,它会要求孵化项目发布时必须带 incuabting 字样和带有一份 DISCLAIMER 文件,来表示孵化项目并不一定完全符合 ASF 承诺的软件标准。

参考案例

再谈 Rust 项目社群治理

作者 tison
2023年11月18日 08:00

上一篇文章《Rust 社群何以走到今天?》发布之后,得益于 Rust 社群的繁荣和成员的分享惯例,我收到了不少关于 Rust 现状的评论。

大部分评论集中在 Rust 项目社群的治理上,即 BDFL 和基金会会对 Rust 项目社群产生什么影响,以及 Rust 项目社群是否需要 Leadership 组织。

我在上一篇文章中引用了 Graydon Hoare 和 @withoutboats 等人的博文,展示了“遗老”们对 Rust 项目社群目前的一些看法,这不免让人理解成赞同他们的观点,希望 Rust 社群出现一个强有力的领导人,甚至希望 Rust 语言像 Graydon 设计的那样演进。

其实,上一篇文章主要想说明的只有两个观点:

  1. Rust 项目社群治理的核心特点是没有一个人(BDFL)或一个一致行动的团体(Apache Group)说了算。
  2. Rust 项目社群未来的发展会极大受到各大公司组成的基金会的影响。

我并不认为有 BDFL 就一定是好事或是必须,至少我所在的每个 Apache 项目社群中都不存在 BDFL 角色,它们也发展得很好。

而且,以 Rust 项目社群如今的状况而言,几乎不可能出现一个新的 BDFL 或者 Graydon “复辟”的情况。因为拥有《大教堂与集市》中提到的,由“开垦”新项目带来的自然所有权的人,绝大多数已经长期淡出 Rust 项目社群,其他少数几个人也无意于社群管理。对于新人来说,由于没有明确地所有权转让程序,想要赢得 BDFL 的地位,就需要依靠自己的贡献。而以 Rust 项目社群目前的领域范畴来说,这对个人几乎是一个不可能完成的任务。所以,Rust 没有 BDFL 将会是一个长期的现状。

进一步地,作为 Rust 项目最初的赞助者,Mozilla 甚至不在 Rust 基金会的白金或黄金赞助商名单上。这就意味着在公司影响力上,哪怕是 Rust 基金会的创始会员也没有天生的主导权。

从机制上来说,Rust 基金会目前并不参与 Rust 项目的开发,基金会的目的是提供 Rust 项目存续的资金支持,以及相应的人才培养和品牌宣传。

上一篇文章中提到的 Leadership Council 并不隶属于基金会,而是 Rust 项目社群的一个治理机构,其成员由各个 Rust 项目顶级团队组成,基本职责是协调跨团队合作以及针对 Rust 项目社群治理本身的一些问题。

在这之前,Rust 项目社群在基金会和 Leadership Council 的领域内发生过几起争议事件:

  1. Ashley Williams 曾经是 Rust Core Team 的成员,同时是 Rust 基金会筹建时的执行董事,但是后来前后从这两个组织中出局
  2. Rust Moderation Team 因为跟 Core Team 的冲突,以集体辞职表达不满。如今,Core Team 本身被解散,而 Moderation Team 则由两位深度参与 Rust 核心开发的成员支撑。

Rust 社群的这些故事,除去缺少 Linus 这样的 BDFL 维持项目本身的有序运转之外,跟 Rust 社群总是期待一个两全的解法,以及过度期望所谓“专业人士”来解决问题的惯例是有关系的。

前者在 @withoutboats 的博文中有所提及:

我担心 Rust 项目从这个经验中得出了错误的教训。正如 Graydon 在这篇文章中提到的,Rust 开发团队坚持只要有足够的构思和头脑风暴,就能找到每个争议的双赢解决方案,而不是接受有时必须做出艰难决策的事实。Rust 开发团队提出的解决这种无休止的争议所带来的 burn out 的方式,是转向内部讨论。设计决策现在主要以 Zulip 线程和 HackMD 文档等未索引的格式进行记录。在公开表达设计方面,主要是在相关开发者的个人博客上发布。如果你是一个开发团队以外的人,那么你几乎不可能理解 Rust 开发团队认为什么是优先事项,以及这些事项的当前状态如何。

而后者则是一种社会高度分工以后常见的认识误区:其他人比我更专业,只要相信他们就好了;有其他人承担这项分工,我只要给予信任或者进一步给予金钱等利益,就可以完全把这些工作委托出去。

实际上,其他人未必有身临其境的人了解事情的原委沿革,不一定站在项目的立场考虑问题,甚至未必专业。对项目负责的人只能是深度参与的核心成员自己,这也是 Leadership Council 在成立动机中所提到的:

根据核心团队(Core Team)的经验,发现问题和开展实际工作本身,对于一个团队来说都太过繁重。这导致了不理想的结果,在某些情况下,还导致了倦怠。虽然有少量工作需要立即采取紧急行动,但绝大多数工作都需要由专门的管理机构进行跟踪和处理。

此前的 Core Team 和 Moderation Team 不仅相互之间缺乏沟通,甚至本身成员也相对独立于其他顶级团队,这导致本应作为协同社群反馈和各个顶级团队之间合作的两个独立团队,跟其他顶级团队之间也可能缺乏互信基础和合作机制。新的 Leadership Council 成立并以九个顶级团队代表为成员,期望在维持先前 Core Team 所要解决的问题的基础上,提高团队之间的协同效率。

对于基金会,可以明显的看到在机制上它相当于 Linux Foundation 之于 Linux 或是 Python Foundation 之于 Python 的形式,前两者在运行过程中间并未出现如同 Rust 社群这样的戏剧性发展。其原因部分在于它们有一锤定音的 BDFL 主导技术开发和项目团队合作,另一方面也是在这样长期的运作方式下,项目团队成员发展出一套自己解决问题的办法,而不是被不满意的现状困扰,转而祈求外部力量奇迹般的解决问题。

Rust Foundation 的性质是 501(c)6 行业联盟,这跟 Linux Foundation 是一致的,它们存在的目的除了写下来的维持 Rust 的存续发展,从性质上说是要为商业联盟中的成员企业服务的。相反,Python Foundation 和 Perl Foundation 的性质是与 Apache Software Foundation 相同的 501(c)3 慈善组织,纯粹是为项目本身的发展和公众利益服务。与此相匹配地,就是慈善机构型基金会往往在募集资金上不如商业联盟型基金会。

如前所述,由于 Mozilla 无法或不愿入局,如今 Rust Foundation 的四个创始会员分别是 AWS、谷歌、华为和微软。它们并没有像 Oracle 在 JCP 执行委员会中那样继承自 Sum 并再次发展得到的垄断权,甚至 Rust Foundation 本身也没有 JCP 那样直接指导语言项目技术发展方向的权限。但是,既然 Core Team 可以改组,Rust 社群整体对于基金会干涉项目发展存有期望,那么在未来社群治理的发展或者至少每次讨论当中,基金会就有可能发挥出自己的影响力。

当然,现在的主要问题恐怕不是担心基金会如何“窃取” Rust 项目的领导权,而是期望基金会的创始会员和其他赞助商先加大投入,哪怕就是奔着主导权来的,至少先把 Rust 的产业应用和语言发展,投入资源搞起来。但这是另一个话题了。

在基金会之外的另一个角度,也有人在呼唤唯一有可能成为 BDFL 的 Graydon 加入战局“拯救” Rust 项目社群。如前所述,由于 Graydon 本人已经淡出 Rust 社群很久,这实际上是几乎不可能的。

Graydon 本人对此的认识倒是比较清晰,他先后写下两篇博文对此做出回应:

其中第二篇已经在我的上一篇文章里展开介绍过,下面我会翻译第一篇。

译文之前说一点题外话。

Graydon 在第二篇博文里提出的对 Rust 的期待,在我上一篇文章发布后收到了推特上网友的讽刺。从 Graydon 本人列举出来的设计方向看,除了错误处理他更喜欢 Swift 的做法,其他的诸如绿色线程、语言级别集成的容器和迭代器、结构化类型等等,确实都是 Golang 的招牌特性。可以说,Graydon “在位”的话,Rust 恐怕就会发展成弱化的 Golang 而不是今天的 Rust 了。

另外,虽然试图“拥立” Graydon 重回 Rust 项目社群的呼声是不理智的,但是这些呼声的存在,侧面印证了前文提到的 Rust 项目社群总是期待两全解法,并总是寄希望于未知的专业人士神奇的解决问题的惯例。

Rust 项目如何走出当前的境况,我参与得不多无法给出意见。不过,我倒是乐意从开源社群发展的研究角度,来分析 Graydon 这样在卸任之后还持续思考和发生的例子:这确实是不可多得的考察对象。

在不能持续维护项目的作者里,Graydon 的案例非常有参考性。他思考了很多,也分享了很多。但是他没有“成功”。这比那些被“成功”掩盖了问题的例子,还有简单一句 burn out 解释所有问题的例子要有意思的多。

以下 Batten Down Fix Later 译文,“我”指的是 Graydon Hoare 本人。

在社交网站上,有人问我:“你是否希望自己是 Rust 项目的 BDFL?如果 Rust 项目有一个 BDFL,现在这些戏剧性事件会不会少一些?”

这是一个很难回答的问题。如果只看它表面的提问,我的答案是“不希望”和“不会”。但我认为它还间接地问了另一个问题,即 Rust 项目社群治理中是否存在某种原罪,或是致命缺陷,例如缺少 BDFL 的角色,而这正是其频繁发生内部政治问题的罪魁祸首?

关于这个问题,我可以分享一些我的观点,希望能简明扼要,或者至少具体一点。我认为有几个根本原因和一个主要的模式。

主要模式

我已经有十年没有参与这个项目了,所以我认为我说的任何话都需要谨慎对待。但我确实时不时地关注 Rust 项目的进展:这些年来,有不少人离开了这个项目,而且其中许多人的离开是不愉快的。很多人对参与这个项目感到后悔和怨恨。这令人悲伤。

我认为这些事情通常是这样发展的:

  1. 项目内部存在冲突。
  2. 这个冲突没有得到很好的管理或解决,至少没有以专业或健康的方式处理。
  3. 冲突并没有消失;相反,它以某种不专业或不健康的方式被表现出来:有时是一次性爆发,但更常见的是长期存在。
  4. 可能在经历了第三阶段的一段时间后,冲突的一方就离开了项目,而所有人都试图假装冲突从未发生过。

这里所谓的不专业或不健康的处理方式,我至少见过四种具体的形式:

  1. 利用非常规的力量:压力、社交影响力、小圈子。
  2. 利用外部正式权力:找某人的上司。
  3. 利用内部正式权力:与管理员进行交涉。
  4. 通过花费时间争论消耗对手的精力。

离开阶段常常显得有点突然,因为其原因不透明,并且也有几种不同的形式,通常对应到上述处理的模式:

  1. 因厌恶甚至为表抗议而辞职。
  2. 上司处理或解雇。
  3. 管理员处理或封禁。
  4. 由于精疲力竭或倦怠而退出。

理解模式

所有这些阶段都有可以理解的缘由。我不是说它们是“好”的,但可以理解。我理解它们是如何发生的,甚至可能对形成这些模式起过推波助澜的作用。

以下是一些背景事实,有助于理解为什么会出现上面这些模式:

第一,很多人只是习惯性地避免冲突。这可能是由于成长背景、个人创伤经历、固有的天性或其他原因。我自己就是避免冲突的人!应对冲突是困难且疲惫的,尤其对于没有报酬的志愿者来说。避免冲突通常看起来更容易。冲突回避是一种为了当下舒服一点,将问题推迟至未来应对的方式。

第二,Rust 如今形成了一种非常高风险的品牌。它似乎非常不可思议,经常吸引大量的互联网关注。它一度似乎被普遍崇拜,又被普遍憎恨。它感觉随时可能失败,但又感觉总是处于胜利的边缘。这在许多人中产生了一种自卫心理,我相信助长了这种心态的蔓延(我是一个焦虑的偏执狂),我确信这种心态是让许多人全身心地投入其中的原因之一。这可能是有趣和令人满足的,但对于许多人来说,它变得更重要。这些人为了参与 Rust 相关的事业付出了全部,将自己的职业或目标感、身份投入其中。自己的工作和未来往往岌岌可危!而且很多人也因过度工作、过度努力而真正精疲力竭。那些没有精疲力竭的人往往保留着一种内在的承诺,这使得缓和冲突变得困难,妥协变得困难,甚至公开承认问题也变得困难。

第三,Rust 最奇怪的文化规范之一(我可能有助于形成它,如果是这样,我向你道歉),是许多人都相信所有的冲突都是“虚假的权衡需求”,即冲突显示需要权衡的地方,都可以像 Rust 声称解决速度与安全之间的权衡问题那样,不用权衡地取得双赢。所有事情都可以实现双赢,如果你找不到这样的解决方案,那就是你没有努力尝试。我的意思是,如果真的能双赢,那当然很好,但是有时事情只能在合理的权衡甚至一定的冲突中形成决定!有些事情是零和博弈的。

第四,Rust 项目社群在管理或解决冲突方面没什么正式机构,考虑到 Rust 项目社群的规模,这个问题尤为突出。Rust 项目社群是互联网上的志愿者构建的,并在一个自身没有强大的正式架构的组织中孵化(Mozilla)。我推荐你阅读《无架构的暴政》这篇文章。Rust 项目社群中只存在着非正式的架构,或者执行严格的最终手段的正式架构,例如 moderators 或者外部干涉。

最后,这些非正式架构当中,有一种普遍存在于 Rust 和许多开源项目当中,这就是时间承诺的规范。《无架构的暴政》中也提到了这一点。热情的贡献者通常愿意并且有能力在项目上投入大量时间。然而,并不是所有人都能够遵守这种时间承诺。付出时间往往被明确激励:“真正做事的人有权做决策”。但是,这些决策往往会影响到许多其他的利益相关者,而“拥有最多时间的人”可能无法很好地代表这些利益相关者,或者可能缺乏完成这项任务所需的技能或知识。而“比其他人花更多时间”的做法也是一种相当不健康的处理冲突的方式:与其直接面对冲突,不如跟对方比拼时间投入,互相消耗精力。即使在正式架构内部,如果有“每单位时间贡献多少输入”的余地,这种做法也可以被采用。有些人会出现在每个会议上,推动相同的议程。这种做法行之有效,你能够按照自己的方式推进事情,但从社群角度上看,这并不是最好的方案。

修复问题

我真的不知道如何解决这些问题。如果我知道,我肯定会提出建议。我之前说过,我对助长一些模式感到相当负责。不过当然,过去的已经过去了。对项目来说,最重要的是未来。

我想我的主要建议是一个“不要听我的建议”的建议:“雇佣并听取那些在相关领域接受过培训的专业人士”的建议,其中“相关领域”涵盖了“一群编译器极客们”通常不擅长的一切。从项目管理到政治科学、金融、传媒、调解、人事管理。Rust 项目现在是一个相当大,而且非常分散的组织,人们长期以来一直在研究如何运行这类组织,甚至不同的专家研究其中不同的细分领域。应当听取他们的意见,而不是试图从某个第一性原则开始解决每个问题,也不要假装因为你们是一群互联网上的编译器极客就可以回避一个正常组织的所有机制。

我不知道新的治理体系在多大程度上具有这些专业人士的痕迹,也不知道它是否会在很大程度上解决上面提到的问题模式,但我可能会对结果感到惊讶。我在这些领域没有专业技能!如果一定要说,我喜欢“权力明确划分”、“任期限制”和“角色轮换以避免倦怠”这样的概念,以及透明决策;我喜欢设置某种利益相关者的代表和限制时间投入的机制。

我不知道社群是否承认现实中存在冲突,以及是否必须明确解决冲突。我不知道当前的治理模式和治理机构,在使项目中的权力职位,包括非正式的权力职位,对其行为负责方面,或在公开宣传以树立信心方面,是否做得足够。最主要的是:我不知道现在的治理模式,是否能够让那些不想全身心投入项目的人过上舒适的生活。

就我个人而言,我根本没有足够的精力,这一切对我来说都太沉重了。对于那些参与其中的人,我祝你们一切顺利。祝好运。

追加讨论:基金会

我想明确一点:据我所知,Rust 项目社群中的治理问题并不是 Rust 基金会导致的。每当社群中发生一些争议,总有一群人会把矛头指向基金会,这通常是错误的。

再次强调,我只是一个从旁观者的角度观察和倾听的外人。基金会通常似乎是房间里的成年人,当它做出一些表面上看起来奇怪的举动时,通常是因为它试图解决项目交给它的一些不可能解决的困境。

我认为基金会实际上只是想支持这个项目的存续,而 Rust 项目一直不是一个很容易支持的对象。

追加讨论:企业赞助商

此外,虽然 Rust 项目在一定程度上是由志愿者推动的,但它也在一定程度上是由企业赞助的,并且不仅是基金会成员的赞助。尽管有时我认为企业赞助会对维护者产生不良影响,导致维护者没有动力做那些琐碎的维护工作,但我认为 Rust 不会出现人们最担心的情况:企业在冲突或不受欢迎的决策中“购买影响力”,或者以其他方式操纵这门语言的发展。

这是一个可能存在的问题,但基金会的架构在设计之初,就花费了很大的精力来最小化相关风险。到目前为止,我认为我们没有看到这种情况。

追加讨论:最初的问题(BDFL)

为了更加具体地解释本文开头的“不希望”和“不会”的回答:我不喜欢受到关注或压力,当我在 2009 年至 2013 年担任项目技术负责人时,我接近极限运作,我离开的一部分原因是达到了这些极限并且有些崩溃了(以及公司对我燃尽的事实的反应并不好:参见“每个人都是人”)。我在本文中描述的人性缺陷,在我身上同样存在!

此外,我没有理由相信我会建立起强大或健康的正式决策机制、冲突管理机制或委派和扩展机制。我没有受过有关这些主题的任何培训,我完全是在 Mozilla 的角色中凭直觉行事。Mozilla 本身似乎对这些主题也没什么经验。我曾经试图在 Rust 的设计上进行一次“正式决策”,我试图用投票结果确定关键词命名的选择,但是结果非常糟糕:每个人都讨厌结果。

追加讨论:Moderation

我不了解 Rust Moderation Team 的故事。我从 2013 年其就不在 Rust 项目社群中了,因此,我不希望对他们过去的行为做出任何的评价或暗示,尤其是今天的 Moderation Team 已经不是过去的那一个了。在我看来,这是一个需要知晓内幕的人在其他地方讨论的主题。我想说两件事:

  1. 我写了最初的行为准则,且当时的行为准则更简短、更简单。我同意明确的社群规范和强制执行的能力通常是互联网社群所必须的。我不认为有管理员是一件坏事,也不认为管理员在谨慎和受监督的前提下行使权力是一件坏事。
  2. 我承认版主也是人。他们可能自己违反行为准则,也可能在处理其他人的违反准则行为是不够诚实。我认为这通常很少见,抱怨这种可能性的人往往没有站得住脚的理由,不过这些情况确实可能存在。要求对管理者的行为进行更严格的审查来避免这种监守自盗的可能性也算合理。根据我的经验,大多数好的版主愿意记录他们的决策过程,并解释决策的理由。

追加讨论:小团体和缺乏透明度

拥有志同道合的朋友是很棒的!关起门来做事情,不必向网络上的陌生人解释你所思考或说的每一件小事也是很棒的!这些事情本身并没有问题。实际上,这通常是许多人感到安全、舒适和愿意参与的先决条件。成为一个被审视的公众人物往往令人筋疲力尽,并且可能会排除那些被天性或环境所限的人。但一定程度的透明度是做出对他人产生影响的负责任决策的必要部分,也是权力行使的一部分。

❌
❌