阅读视图

发现新文章,点击刷新页面。

开源嘉年华纪实

上周在北京参与了开源社主办的 2024 中国开源年会。其实相比于有点明显班味的“年会”,我的参会体验更像是经历了一场中国开源的年度嘉年华。这也是在会场和其他参会朋友交流时共同的体验:在开源社的 COSCon 活动上,能够最大限度地一次性见到所有在中国从事开源相关工作的人。

我在本次会场上有两个演讲,分别是 Rust 分论坛上分享的《高性能 Rust 编程:如何减少数据拷贝和并发开销》,以及开源运营分论坛介绍的《商业开源如何重塑社群信任》。本文将简要介绍我的分享内容,以及参会的见闻纪实。

开源社的午后香茗

开源社的所有成员都是志愿者。也就是说,开源社没有任何一个全职员工,所有成员都是依靠对开源的热爱自愿贡献的。我从 2022 年开始志愿参与开源社的工作和活动,从 2023 年开始参与顾问管理委员会提供建议。

今年的 COSCon 正好也是开源社成立十周年的嘉年华,在活动现场作为组织成员有幸收到了开源社准备的一盒普洱茶:

午后香茗

茶客基操

作为一个典型潮州人和饮茶爱好者,这是我本次活动最满意的收获。可以看到,从会场回来三天,每天一颗普洱茶,正好。

主题分享:高性能 Rust

第一天下午我分享的主题是《高性能 Rust 编程》。

我从今年初开始把 Rust 语言作为我的主力编程语言。这主要是我观察到我长期投入的数据系统领域,出现了一个由于云基础设施的崛起带来的创新机会,而这个创新机会的探索者们大多采用 Rust 语言编程和构建生态。于是,我也加入其中。

我的 Rust 码力值

可惜的是,截止我演讲的前一刻,我的 Rust 代码量仍然以 1% 的微弱差距落后于我早年编写 Java 大数据系统时生产的代码。

不过,在过去的一年里,我充分体会到了 Rust 生态的勃勃生机,全面投入到生态共建当中,包括:

本次分享的内容就是在这些工作当中积累的经验,每一页 PPT 都是朴实无华地贴代码讲代码(bushi)。主要介绍的两个核心论点:一个是在 Rust 所有权系统下,如何规避常见的由于绕过所有权挑战带来的拷贝开销;另一个是破除 Async Rust 的性能迷思,介绍其远不及完善的应用现状。

目前,关于 Rust 编程的最佳实践,包括工程组织、接口设计和性能优化,还处于一个激烈讨论的环境中。整个 Rust 生态的繁荣和完整程度,我经常把它比作 Java 1.5-1.7 时代,即当今统治生态的类库和软件尚未诞生或方兴未艾。应该说,Rust 生态的广阔天地,大有可为。任何想要赶上 Rust 主导的软件发展浪潮的人,现在上车还为时未晚。

主题分享:商业开源

第二天下午,我在开源运营和开发者关系论坛分享了《商业开源如何重塑社群信任》的主题。




这个话题其实不太应该出现在“开源运营和开发者关系”论坛,而是比较适合一个独立的“商业开源”论坛,即讨论在商业活动中如何利用开源要素创造优势,还有营利性组织如何做出开源贡献以及背后的原因。

我在做这个分享的时候更多像是对自己近年来的思考做一个总结,从开源项目的分类学,商业开源的方法,到商业开源和“开源商业化”的思辨。这大概会导致信息量太大而听众难以接受。

我目前在实践自己分享里提到的商业开源理念,也颇感有趣的看到我对“开源商业化”的批判接连被印证。或许以后我再讲这个话题的时候,就不会像这次一样做一个总纲式的分享,而是抽取一两个典型的案例和瞬间来传播。

承接上一个主题讲的这一张 PPT 实际不在演讲的 PPT 里🤣

开源社的下一个十年

这个话题其实我讲不了(笑)。只是在这里介绍一下我所认识的开源社,它是做什么的以及未来可能会做什么。

近年来,开源社除了一年一度举办 COSCon 以外,还联合其他社群伙伴推出了《中国开源年度报告》中国开源先锋中国开源码力榜等报告和评选活动,丰富了开源社作为一个公益团体在开源主题上传递出的观点。

但是,相比起开放源代码促进会(OSI)TODO Group 这样,在通用开源和企业开源领域能够提出见解和倡议的组织,开源社在掌握开源话语权这个议题上显得比较弱势。随着中国开源力量的崛起,如何引导各方参与者正确认识开源,在开源环境当中高效协作创新,将是一个无可回避的问题。我热切期望开源社能够发挥自己所在生态位的优势,联合开源社的志愿者,向社会不断输出批判性观点,帮助中国开源茁壮成长。

此外,开源社从去年开始,成立了一系列开源社城市社区(KCC),包括北京、上海、广州、深圳、成都、杭州、长沙、大连、南京和新加坡等。在一年一度的开源嘉年华之外,日常组织起中华开源的 Meetup 活动,日积月累的提高开源理念在群众当中的影响力。如果任何个人或组织想要在本地聚集开源人办活动,都可以考虑联系 KCC 合作落地。

开源嘉年华:交个朋友

最后,我想以“交个朋友”作为本次开源嘉年华参与体验的总结。

这一次 COSCon 的绝大部分分论坛,都是由开源社和一个或多个合作社群共同举办的,所以才能攒成 21 个论坛的盛况。

在主会场上,我能看到开源公益的从业者分享他们的故事,看到青少年开源开发者介绍自己的作品,看到开源人展现自己多彩的生活。

开源从来都是一个广阔的概念,秉持着开放和合作的基本理念在不同的细分领域创新价值。但是,开源也只是每个开源人生活当中的一部分。本届开源嘉年华的主题“开源新生活(Open Source, Open Life)”,传递出的是开源社一直以来的人文关怀。

希望所有参与开源的贡献者,都能在开源共同体当中认识新朋友,开心开源,在开源生活中收获快乐 :D

尝试升级 WordPress 版本 和 PHP 版本,然后失败了

尝试把 WordPress 从 5.2 版本 升级到 6.6 版本,把 PHP 从 7.4 版本升级到 8.3 版本。

任务艰巨得完成不了。

非常的崩溃。


前提

我现在的线上的博客,以及线下的开发环境,以及(如果还在不为我知的某个角落存在的话)商用用环境,都是 PHP 7.4 的。

最早些时候,应该算是2015年,正式入坑 PHP ,那时候基本是公司用啥,我用啥。PHP 5.2 5.4 5.6 都用过了,尤其是 5.2 大坑一大堆,摔过很多次。然后到 2016 年的时候终于用上了革新的版本 PHP 7.0。真爽。再后来开始自己独立开发环境,直接开了 DAMP 的坑,最初也是用的 7.0 版本,然后就很随意的升到 7.3 版本,主要是当时开发负担少。然后博客站也都升级到了 PHP 7.3。

再后来 2021 年接了一个外包项目。虽然当时 PHP 8.0 已经发布了,但是貌似周边支持都不怎么地。为了保证开发速度,直接用了 PHP 7.4 版本。当时还涉及到前端开发,Node环境也是大更新,很早之前一直用的各种热门前端库基本都死绝了。博客和本地开发环境也都升级到了 PHP 7.4,博客的 WordPress 程序倒是没变,还是 5.2 版本(这里埋了个大坑),只是偶尔会从官网下载代码包然后手动更新对应的文件,以修复些潜在的安全问题。

现在是2024年年末,就连PHP 8.0 都已经停止维护快满一年了,最新版本 PHP 8.3 也已经发布快满一年了。WordPress 都到了 6.6 版本了,一堆 WordPress 插件都已经停止 5.2 版本的支持了。

想趁着有时间有机会,把 PHP 和 WordPress 都升级了。

天真了。


第一天

首先是把线上的代码全备份一遍。这个毫无工作量,我之前写了个备份代码,直接就把自己的整站扒下来。然后把备份的站再部署到本地的开发环境上,再改几个数据库字段,就完事了。打开后台,先禁用所有插件,以便升级之需。

Apache2 和 MySQL 不用动,因为用的一直都是 Docker 的最新版本,这几年也没什么巨大的兼容性变化。

PHP 这块我不知道算不算麻烦。我把 PHP 从 7.4 到 8.3 的所有 不向后兼容变更废弃功能 全都看了一遍。没多少,就几页,几分钟就看完了。个人感觉这么多变更,只有一条能实际影响到我日常开发工作,就是自 8.0 起不再支持 带有默认值的参数后面跟着一个必要的参数

screenshot_on_b85m_by_flameshot_at_2024-10-24_22-51-45.png

其实个人工作中也很少这么用,因为易读性有点烂。论性能来讲的话这么写性能也很差,只不过以前工作过的公司里就有很多人这么写,主要是为了防止其他同事调用函数忘记传参(空参也是参)。

其余的改动基本影响不到我。我使用的基本都是 PHP 的官方建议用法,最多也就是会遇到某些外部库被遗弃然后有个平替的情况,比如数据库接口啥的,即使出错了立刻就能发现。

于是很自信的先把 PHP 环境升级到了 8.3 。因为是基于 Docker 的所以也完全不用担心环境污染的问题。

然后就崩了,WordPress 就打不开了。

这倒是意料之中,毕竟当年 WordPress 4.3 版本当年连 PHP 7.0 都不支持。我现在用的是 5.2 版本,最高能支持的 PHP 版本也才 7.3 ……

Screenshot_2024-10-24_at_23-02-46.png

什么?7.3?但我已经用 PHP 7.4 跑了 WordPress 满 3 年了啊???

什么兼容性测试……

下载了个 wordpress-6.6.2 的包,按官方文档手动安装。 崩得一塌糊涂

全部删除了重新来,这回用官方的自动升级功能,直接从 5.2 升级 6.6。真神奇,WordPress 官网被墙了这么久了竟然能秒下 WordPress 的安装包,我也不知道他是走的什么渠道。PHP 就这点恶心,前台所有操作你都看不到任何细节,就像是在用 Windows 一样。

成功安装。然后 崩得一塌糊涂 。这回不仅崩得稀烂,而且由于没手工删除后台的旧文件,新旧文件混在一起,更是手足无措。

就这么搞了超过6个小时,一直干到后半夜三点多,搞不定。

放弃,睡觉。

然后严重失眠,抽搐。

screen_IMG_2148.png


第二天 白天

首先考虑下到底是 PHP 的问题还是 WordPress 的问题。

按理来讲我已经把 PHP 的升级文档都看完了,并没有什么会天塌一般的变更,但是 WordPress 这边的确天塌了。

先在 PHP 8.3 环境下运行一下我的其他项目看看,结果我的个人主页就崩了。

screenshot_on_b85m_by_flameshot_at_2024-10-24_23-33-27.png

我用的是 2023 年 3.1.48 版本 的 Smarty,其基础版本是给 PHP 7.0 做的,可能旧了吧。

下载了最新版本的 smarty-5.4.1 ,然后

screenshot_on_b85m_by_flameshot_at_2024-10-24_15-36-49.png

什么玩意? implode is Deprecated ,我怎么不知道?

又去重新看了一遍 PHP.net 官网的 implode 文档和 PHP 升级文档。根本没有 Deprecated 。

然后在网上搜了一下,发现是 Smarty 的锅。而且 Smarty 还在 join 和 implode 之间反复横跳。更恶心的是,implode 和 join 在 PHP 7.4/8.0 中已经声明并废弃了 先数组后分隔符 的用法,但是在 Smarty 中却是强制要求 先数组后分隔符 ?甚至官方在 issue 里来来了句 Smarty is Smarty and PHP is PHP. 有病吧。而且你就算有病,你特么连个文档都没有,谁知道你有这种抽风的设计啊。

你要知道之所以我还用 Smarty 这种超级古董,就是因为这是一种 靠谱的、前后端分离、完全后端渲染、仅需要 HTTP 和 PHP 环境、不需要臃肿框架和特定语法,的网页渲染模式,是提供给搜索引擎最靠谱的传统模式。没有网页模板系统的话,想写这种纯后端渲染前端显示的页面,就只能 php 和 html 代码混写,非常的恶心。

现在 Smarty 抽风了,真就不知道以后还怎么不依赖框架写这种页面。我搜了下,Laravel 的 Blade ,和 Symfony 框架下的 Twig,貌似也可以独立使用,但是我对 Laravel 和 Symfony 基本一无所知,作为一个 PHP 开发者真是有点丢人。

话题扯远了,回到刚才。

我目前是不太想动我的个人主页的,这 WordPress 是大头,是主要内容。主页只是个入口。虽然主页改动起来并不困难,代码量少,Smarty 抽风的部分比较好找。但主页这一块我其实并不满意,主要是多语言这块用的传参而不是独立页面,很受搜索引擎嫌弃,基本上没收录,收录的也搜不到。但我的确没有精力和欲望去重做。

另一方面,WordPress这边,崩溃得最多的部分其实是插件。WordPress的代码质量本来就很堪忧了,第三方插件更是三脚猫,各种天花乱坠的不规范语法,可以说基本上都不能在 PHP 8.3 上运行。

另外我虽然看了 不向后兼容变更废弃功能 ,但是 PHP 新版新增的语法糖也有点天花乱坠。我用的是最新版的 php-cs-fixer_v3.64.0 ,没配置好的话就会把那些插件的奇葩写法转写成 8.3 的语法糖,有时结果更是瞎眼,基本没有易读性。

综上考虑,先放弃 PHP 8.3 的升级。先把能通的条通。PHP 的升级难度应该不是最高的,但是底下这些撇不掉的小垃圾目前是必须要保且支持不到 PHP 8.3,没精力做修改。

放弃 PHP 8.3 继续用 PHP 7.4 。


第二天 晚上

看兼容性列表,WordPress 6.6 也是支持 PHP 7.4 的。

但是实际上 API 改动实在太大了,而 PHP 前端应用最恶心的一点,就是 出错了,不报错

即使开启了 define('WP_DEBUG', true); define( 'WP_DEBUG_LOG', true ); define( 'SAVEQUERIES', true ); ,也经常是

  • 功能好像开了但是没开
  • 功能好像崩了但是啥日志都没有
  • 功能正常使用但是页面上打了一堆不知道哪里来的错误日志

面对如此大的一个工程而大部分代码逻辑都是不可靠的。升级到 6.6 实在是消受不起。

放弃 WordPress 6.6 ,只升级到比较近的版本。

要不然试试 5.5 吧,毕竟我在用的一款插件,作者自评最高支持到 5.5。

下了个 WordPress 5.5 的安装包,装完了。崩,但是崩得没有 6.6 多。

主题 graphene

不开插件,只看主题,首先就是文章的评论显示不出来。检查后发现是评论的API变了,而我的主题 graphene 是 1.9.4.3 版本的,不支持 5.5 。

screenshot_on_b85m_by_flameshot_at_2024-10-25_00-48-04.png

有时候看其他人的代码就是折磨,代码里写法五花八门,空格TAB混着,一会拼接字符串,一会替换字符串。单引号双引号混着用,左边括号有空格,右边括号换行了。

说实话我都不知道这算不算改好了,反正现在是能显示出来。我更担心的是其实还有哪个不知道的角落还有错,但是看不到,毕竟 PHP 前端 出错了 不报错

官方倒是有个新版的 graphene,但是我现在用的这个主题就是我大量改动过的,因为 graphene 原版的代码实在是, 太错了 。好多代码完全不符合前端的理念。然后是颜色和界面也是要一点一点从设置里调,那复杂和麻烦程度,说真的我更乐意重写 HTML 和 CSS 。

我真的想过很多次自己做一个 WordPress 主题,这个想法可是足够老了。但是当时 WordPress 3 版本的主题文档就恶心到我了,真的超级麻烦。而到如今就 WordPress 现在的代码质量,我估计开发主题会更困难更恶心。

也是怪不得其他更轻量的博客程序能后追直上。

插件 Disable WordPress Core Updates

接下来是 Disable WordPress Core Updates 这个插件。

这个插件是为了禁用 WordPress 的界面更新的。但是其实只有一行有意义的代码:

add_filter( 'pre_site_transient_update_core', create_function( '$a', "return null;" ) );

首先 create_function 这个函数在 PHP 7.2 废弃,在 8.0 中删除,所以我改写成了。

add_filter('pre_site_transient_update_core', function ($a) { return null; });

然后就见证奇迹了。能用是能用,但是 pre_site_transient_update_core 这个字段我在整个 WordPress 代码中都没找到。为什么做一个 pre_site_transient_update_core add_filter 就能抑制 WordPress 界面提示升级? 魔法啊?

然后开启这个插件的时候,更新页面是崩的。

screenshot_on_b85m_by_flameshot_at_2024-10-24_21-14-55.png

魔法。

插件 NIX Gravatar Cache

接下来是 NIX Gravatar Cache 插件。

这是个把 Gravatar 头像缓存到本地服务器的插件,只不过早就死透了。我当时随便改了点代码对付着用,大部分时间没出错也就那样了。

首先是这段代码。(红色部分是我添加的改动)

screenshot_on_b85m_by_flameshot_at_2024-10-25_01-04-29.png

不能直接执行 wp_enqueue_scriptwp_enqueue_style ,要先执行个 add_action('wp_enqueue_scripts', 引用能调用那俩玩意的函数); 。外国人看这种超长的单词时不会眼花吗?

然后是这么一段代码。

screenshot_on_b85m_by_flameshot_at_2024-10-25_01-00-47.png

WordPress 的 register_啥啥啥_hook(__FILE__, array($this, '函数名')); 写法全都作废了,要改成 register_啥啥啥_hook(__FILE__, array($this, '函数名'));

接下来底下那一节:

  • 假如 路径不可写 且 路径为目录,报错

否则

  • 假如 创建目录(权限777)失败 且 路径不为目录,报错

就这烂判断条件看得我脑子都快炸了也没弄明白为什么这破玩意能在我线上服务器上跑几年没报错,而我本地开发环境却根本跑不通。

screenshot_on_b85m_by_flameshot_at_2024-10-25_01-19-42.png

而且这玩意讨厌就讨厌在于,确在我的测试环境下报错了,但是 WordPress 只是多了个 .php-error 的样式并且高出来 2em 的一节,但是一点错误日志都没有!!! 出错了 不报错

最后还是靠自己写 debug 代码定位的问题。

这 TM 都是些不该是问题的问题,竟然多得到处都是。

2天,一点有效进展都没有。唯一有效收获就是这些屎山不碰就没事,一碰能崩得全身是屎。

反而自己写的没有引用那些垃圾玩意的插件和程序,没发现啥大毛病。


第三天

2天没啥进展,给我干懵了。

俗话说没事别升级,升级必出事。

原本的想法是先试着升级,如果不能平滑升级的话,大不了全摧毁了,然后把整个博客文章用导入的方法塞回去重建。如果插件出问题了,大不了找找看是否有新的替代品。结果搜了一下,靠谱的插件基本没有,一大堆商业推广的插件,和一大堆复合性插件,而且这些插件无论对 5.x 版本 还是 6.x 版本的兼容性都乱七八糟,问题解决不了,还有可能引入更多的问题。主题这边则是更不想换,一方面本身现在用的主题就是我大量修改过的,因为网上的各种主题,仅安全性就一塌糊涂,更别说 HTML 标准了。我这主题还是专门针对 1366×768 分辨率优化过的,能在 小屏幕 150% 比例正常显示。让我再去改个新的,工作量也是太大。

现在基本上没辙了。

PHP 这边其实还行,而且版本活跃度比较稳定。

screenshot_on_b85m_by_flameshot_at_2024-10-25_22-09-00.png

https://packagist.org/php-statistics 能看出各版本使用占比都跟维护相关。但 7.4 版本 比 8.0 版本还受欢迎 属实乐了。

但是周边应用真的是质量山体滑坡。WordPress自 3.0 版本就开始崩,后面很多发展都很魔幻,就连编辑器都是靠社区兜底,到现在已经想不清楚这玩意的产品路线是啥了。

其他生态我也不清楚,毕竟作为一个 PHP 开发,我连 Laravel 和 Symfony 都没用过,商用产品都是用 ThinkPHP 应付的,当然最爽不过不依赖框架没有条条框框自己从头写。

但是像 Smarty 这种原行业标志都走奇葩路线了。可以说整个IT行业,基本上,正常的元老人物都退出舞台了,剩下的这些,刨去臭鱼烂虾,就只有偏执而扭曲了,假若走向歪路,那就没得旧,而这一点在开源社区上也极为明显(因为闭源商业的死不死没人关心),Godot 基金会开搞政治正确炮轰特朗普和动画头像用户,Linux 基金会直接开踢俄罗斯的代码贡献者。IT 行业现在就像是一个患了早期癌症的癌细胞轻微扩散病人,看似有救但却是谁都不想救。


结论

给我干哑火了,懵逼了,现在不知道咋整了。

The post 尝试升级 WordPress 版本 和 PHP 版本,然后失败了 first appeared on 石樱灯笼博客.

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

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

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 标准库开发看开源贡献的源动力

首先介绍一下我在 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 参会纪实

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 湾区线下聚会纪实

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 语法:一次开源贡献的经历

本文是 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][腾讯微云]

声明:

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

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

  • 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 的演变

近年来,时序数据的增长是 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 之路

『太长不看版』

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 程序库生态合作的例子

近期主要时间都在适应产品市场(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 孵化器中帮助项目

由于 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

我在《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 纪元

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

代码、法律和自由软件

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

Git 纪元

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

GreptimeDB 社群观察报告

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 毕业随感

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 社群理念和方法论的人,成长为基金会成员的一条康庄大道。

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

近几年,国内开源项目捐赠到 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 同侪社群的理念和工作方式,以减少项目在面临不合理的挑战时遭受的挫败,尤其是当它来自于某个看起来权威的成员时。

❌