阅读视图

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

尝试升级 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 石樱灯笼博客.

[网盘] 任天堂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 石樱灯笼博客.

面向人力资源的 GitHub 指南

越来越多的开源软件占领了各个领域依赖链条的关键环节,越来越多的程序员也以参与知名开源软件的开发为荣,将开源贡献和在开源社群当中获得的头衔作为简历当中浓墨重彩的一部分。

在这样的背景下,技术驱动型公司的 HR 也随之需要掌握从开源社群当中找到组织需要的人才的方法。如果企业的业务依托于开源软件的发展,甚至公司直接投入人力参与开源社群,那么 HR 还需要了解开源项目运作的基本流程。

我在最近三年间陆续遇到人力资源相关的从业者询问如何解决这两个问题。目前,大部分的开源项目代码都托管在 GitHub 平台上。本文从我接触到的问题出发,从 GitHub 提供的能力和 GitHub 上项目协作的常见形式入手,回答这两个问题。

发现人才

由于 GitHub 没有像 LinkedIn 那样提供直接的 endorsement 机制,HR 无法简单的通过标签筛选来圈出符合要求的候选人。GitHub 上的活动几乎全是围绕着软件仓库进行的,因此最合适的筛选条件,就是根据职位描述上要求掌握的开源软件技能,到相应的代码仓库当中查看活跃的参与者。

第一步,找到要求对应的代码仓库地址。

如果业务团队已经标明相关的开源软件,例如流式计算工程师最好掌握 Storm / Flink / Spark Streaming 等技能,那么使用 GitHub + 对应词组的关键词 Google 搜索,认准 github.com 地址的网页,很容易就能导航到对应的代码仓库。

Google Search Flink

否则,如果业务团队只提供了模糊的关键词,就需要先发现对应的仓库。一般来说,最好是反馈到业务团队重新提供候选仓库,哪怕只有一个,也可以从代码仓库为自己打上的标签里找到靠谱的那个点进去发现其他类似的仓库。

FoundationDB 的标签

例如想要招募存储方向的人才,如果知道了 FoundationDB 是符合条件的,从标签里的 key-value-store / transactional / distributed-database 都可以顺藤摸瓜抓出一批来。

如果想要再仔细的圈选,就需要数据分析团队或者开源社群运营团队开发对应的分析工具了。比如基于 ClickHouse 的 GitHub 事件公开数据集,可以以提交补丁为维度圈选出关系最紧密的项目。下面就是一个发现和 Apache Flink 项目相关的其他项目的示例。

基于 ClickHouse 的公开数据集发现关联项目

第二步,从代码仓库的 Contributors 页面发现活跃的参与者。

跳转 Contributors 页面

例如从 TiDB 的 Contributors 页面当中,我们就可以看到 @tiancaiamao / @coocood / @zimulala / @crazycs520 几位是提交最多 commit 的开发者。GitHub 支持以 commit 数或代码增减行数来排序,最多显示前一百名参与者。虽然有其他手段列出所有有代码提交的参与者,但是对于圈选候选人来说,前一百名应该已经足够。

TiDB Contributors

另外,默认的排序是基于历史全部事件的。HR 圈选的时候可能会更关注最近还活跃在项目上的开发者,这点可以通过鼠标在上图的时间线里拖出一段时间来筛选。下图显示出从 2021 年至今最活跃的参与者。可以看到 @coocood 工作重点转向之后前一百名都看不到人了,而虽然我在图上名列第四,但是具体曲线图可以看出和前三名相比,我在最近一段时间都没有新的 commit 了。

TiDB Contributors after 2021

第三步,全面评估圈选出的候选人,并找到联系方式。

现在,我们找到了一批不错的候选人。那么接着就需要做一些简单的背景调查和找到候选人的联系方式了。

通过点击 Contributors 页面参与者的头像或用户名,或者直接输入 http://github.com/ + 用户名可以看到候选人的 GitHub 个人主页。

@PragmaTwice 个人主页

例如,想要招聘熟悉分布式系统开发的 C++ 工程师,从 Kvrocks 或 OneFlow 等项目里发现 @PragmaTwice 之后,查阅 @PragmaTwice 的个人主页

  1. 可以看到,他在自己的个人主页里有 Open to graduate job opportunities! 的描述,可以看出他是一个正在求职的应届生。
  2. 接着看他的个人简介,是有精心设计过的,至少不是三无用户,可以交给业务团队负责人进一步筛选。
  3. 从下面绿色砖块的代码提交墙上可以看出,他在过去一年当中积极活跃在 GitHub 上。
  4. 上图界面再往下拉,可以看到他的贡献 67% 是代码提交,code review 和 PR 都在 15% 左右,issue 仅占百分之五。这是一个典型的独立开发者,并且大部分时间都直接推送 commit 到仓库上,在团队协作上暂时不能看出是否具备足够的经验。
  5. 最后看 Pin 的项目,这里只能展示用户曾经参与开发的项目,也就是候选人是这些项目的开发者。@PragmaTwice 参与过两个知名项目 OneFlow 和 Kvrocks 的开发,他的个人项目 protopuf 和 proxinject 都收获了还算不错的 star 数(不能被少部分头部项目误导,绝大部分项目根本没有几个 star 的。Open Collective 设置的门槛是 100 stars 已经是个比较有效的筛选器了),可以点进这些项目筛选出他实际的参与内容,进一步分析。

上面列举了几个可以参考的角度。实际上,评估一个候选人不是死板的套公式,往往需要启发式地发现候选人的亮点。一般来说,从他参与过的项目和参与项目的具体内容可以做出一定的判断。由于 GitHub 上的身份只是候选人的一个侧面,我建议 HR 尽量从发掘亮点的角度看待,而不能从 GitHub 的活动上缺少什么就推断出候选人缺少什么。

再介绍其他一些常见的评估工具和角度。要想看到一个候选人在 GitHub 上全时间的活跃情况,从个人页一年一年看是一种方式,但是也可以在 github-contributions 网站上输入用户名直接列出来会简便一些。

@tisonkun's Contributions

可以看到,我从 2017 年底开始涉足开源软件开发。陆续参与到 2020 年 5 月加入拼多多,明显出现了一段真空期。2021 年初加入 PingCAP 后工作又和开源软件相关,活跃程度逐渐恢复并超越过去。当然,从 HR 的角度没办法知道这么详细的个人工作变动,但是可以从活跃度的变化做出相应的假设并验证,勾勒出候选人的工作经历。

另一个问题是前面提到的确认候选人贡献的质量。

如果想要不区分代码仓库的查看一个 GitHub 用户的活动,可以从以下界面搜索筛选。

GitHub 用户搜索

这个页面的搜索功能是很丰富的,除了直接从用户界面的按钮探索以外,也可以从 GitHub 搜索文档里发现更多的筛选方法,这里就不展开了。

如果想要具体看一个 GitHub 用户在某个仓库的参与,可以用以下几个链接来调查,其中的用户名部分相应替换。

这三个链接分别展示了用户在当前仓库的 commits / issues / PRs 活动。同样,从用户界面上或 GitHub 的搜索文档里可以发现更丰富的筛选方式,这里尤其想提出的是 involvesreviewed-by 筛选器,分别能够看到用户参与的 issue 和 review 的补丁。

根据 HR 对行业的熟悉程度,可以从这些具体的活动当中对候选人做进一步的判断。基本地,如果用户的 commit 基本是 fix typo 字样的,那么他在这个仓库的参与就很有限,这个项目的声誉并不能为候选人站台。

最后,HR 需要找到候选人的联系方式发出邀约。

有些用户(包括我)直接在个人页就展示了个人邮箱和推特账号,可以很容易地发送邀约。

此外,类似 @PragmaTwice 这样主动求职的用户,往往会用醒目的手段留下自己的联系方式。他的个人主页里包括这样一段代码:

1
echo -n "My email address: " && echo QkVzzAyYQ0kMoVEH0mihz7zDbk6aalkDYvfnW1OaccM= | openssl enc -d -base64 | openssl enc -d -aes-128-cbc -iv 205731624 -K 230549126 2>/dev/null

把它放到终端里执行,会得到以下输出:

1
My email address: twice@apache.org

这就是典型的程序员介绍自己的方式,一方面展现了自己的黑客精神,另一方面也避免纯粹爬取个人信息发送邮件的机器人的骚扰。

对于没有公开邮箱的用户,就得采用一些特殊的方法来挖掘了。我是通过下载下来候选人参与的仓库,用 git log 命令查看找到候选人提交的 commit 上的个人信息。比如我的提交显示成:

1
2
3
4
5
6
7
commit 9053519c0b81b765919aad9a9695910580586ea1 (origin/main, origin/HEAD)
Author: tison <wander4096@gmail.com>
Date: Sun Aug 21 23:07:49 2022 +0800

post over-communication

Signed-off-by: tison <wander4096@gmail.com>

这里就有我的个人邮箱。

由于 git log 里的 commit 信息跟 GitHub 的用户名未必有关系,把候选人和具体某个 commit 的 author 对于起来需要一些洞察力。

理解项目管理

对于商业模式依托于开源软件,并且大力投入到项目开发的公司来说,托管在 GitHub 上的项目承担着公司研发部门重要乃至主要的工作内容。HRBP 和参与绩效制定的 COE 员工有时也会希望理解 GitHub 上项目管理的基本方法,我以回答被问过的问题的角度做个简单的介绍。当然,项目管理的方法维护者各有所爱,也有些项目根本不遵守业内的共识,这里介绍的是经过选择的一些我个人比较认同的方法。

第一个,研发通常是一个一个版本的发布软件,软件版本是如何确定的,版本之间又有什么关系呢?

首先需要知道的一点,软件版本发布后就是不可变的了。比如,Pulsar 2.10.1 版本发布后,就一行代码,一个字符也不会再动了。哪怕代码里面有 BUG 要修,也是通过发布一个 2.10.2 版本的方式在 2.10.1 版本的基础上包括修复的补丁,而不是直接改动已发布的版本。实际上,类似 Java 生态的 Maven 中央资源库和 Rust 生态的 Cargo 资源库,都不允许修改已经发布的版本的内容。

这个很好理解,如果同一个版本号,今天拉取是一个内容,明天拉取是另一个内容,用户的构建就不稳定,不可预测了。尤其是开发者虽然希望在新版本里修复缺陷,但是谁又能肯定新的变更不会引入新的问题呢?对于开发者来说,确保版本号对应确定的内容,也是排查问题的重要前提。我曾经见过私有化部署当中同一个版本号交付给不同的用户,每个用户后续又提供了各自不同的 bugfix 热更新,到最后开发者根本无法知道某个用户线上的版本到底包含哪些改动不包含哪些改动,很难排查线上问题。

因此,合理的软件,同一个版本对应着相同的软件逻辑,相同版本的两个软件包,行为是完全一样的。在这个基础上,业界在长期实践的基础上逐渐聚拢到语义化版本的标准上。

语义化版本以形如 X.Y.Z 的版本号来命名软件版本,上面提到的 Pulsar 2.10.1 就符合这个标准。其中第一个数字代表“主版本号”,递增主版本号意味着可能引入了不兼容的 API 修改。第二个数字代表“次版本号”,通常意味着新增向下兼容的功能。比如 Flink 1.14.0 发布后,Pulsar Flink Connector 实现的 Sink 最早也只能包含在 1.15.0 当中,而不能进入 1.14.1 等 1.14 系列的后续补丁版本。第三个数字代表“修订号”或“补丁版本”,相同的主版本和次版本,补丁版本越高,包含的缺陷修复越多。新的补丁版本不会引入新功能,更不可能引入不兼容改动,只会包含缺陷修复。

因此,用户遇到 BUG 时,如果不能直接从使用层面解决,往往会选择升级补丁版本,升级补丁版本整体是比较放心的。如果用户想要使用新功能,则需要升级次版本号,升级次版本号需要经过测试,避免新逻辑引入缺陷或回退。如果用户想要的功能需要升级到更高的主版本号,或者由于旧版本不再维护需要升级主版本号,用户一般会严阵以待,准备好迁移不兼容的接口和应对未知的兼容问题。升级主版本号是个重大的决定。

还有其他软件选择不同的版本定义方式。例如,JetBrains 的 IDE 系列用年份 + 当年的第 n 个版本来定义。例如,Linux 总是保持向后兼容,主版本号递增也不意味着不兼容更改。例如,Trino 的版本号只有一个数字,每次发布新版本递增,每个版本之间都可能引入不兼容的改动。例如,Rust 有按照语义化版本发布的流程,也有按照日期每日发布一版的 nightly 系列,还有按需以年份命名的大版本,不同大版本包含默认选项不同。总之,每个项目根据自身特点,可能有不同的策略。

最后,版本号含义的定义只是人为规定的,由于 BUG 随时有可能被不经意的引入,版本号的语义保证并不总是可靠的,唯一可以确定的是软件版本发布后就不可变。

第二个,如何确定哪些 feature 会被包含在下一个版本,如何保证版本计划能够被执行?

这个问题的答案是看情况,没办法客观推理确定。

虽然软件执行的逻辑大部分是确定的,但是软件开发是一个关于人的知识生产过程。从《人月神话》到《人件》,软件工程领域一直以来都在不断强调以人为本的管理理念,但是时至今日仍然有大量的公司在重蹈覆辙。不过,就像其他非确定性的工作一样,虽然我们没有办法像执行程序那样确保版本计划一定按部就班的执行,但是我们还是有一些手段以在社群当中达成共识,最大限度地保障交付高质量软件。

比如,确定研发发布周期。

对于开发活动不那么活跃或者刚起步的项目来说,这点倒不是必要的,往往是社群维护者发现软件已经包含了一系列新的补丁,应当发布新版本了就会开始进入发布流程。这样的软件也很少并行发布补丁版本,往往是线性地递增版本号,如果只包含修复,就递增补丁版本,如果有新功能,就进入下一个次版本。

随着项目聚集起越来越多的开发者,社群维护者总会发现需要在社群当中建立起对研发发布周期的共识,这样才能避免大家都想将自己的变更加入到下一个版本,从而无限延后下一个版本的发布时间。试想,总有开发者会说他的变更将在几天内完成。因此延后几天之后,又会有新的开发者跳出来说他的变更再过几天也能完成。对于一个热闹的项目来说,这并不稀奇,Pulsar 和 TiDB 每天都有十个左右的新提交。

确定研发发布周期,也就是基于时间来发布新版本。比如我见过的分布式系统软件往往以三个月为一个周期,接近周期末尾的时候版本发布的负责人会广播 feature freeze 的消息,也就是在某一天会切出新版本的分支,在那之后分支里就只接受已经包含的功能的巩固和缺陷的修改,不再接受新功能。

至于哪些 feature 会被包含在下一个版本,一般来说,在 feature freeze 并切出新分支之前合并到主干的 feature 就会被包含在下一个版本。当然,也有 feature 的作者对功能不够有信心,主动推迟到再下一个版本发布,甚至把已经合并到主干的部分工作回滚。

对于项目管理者来说,GitHub 提供了 milestone 功能,可以将 issue 或 PR 和某个里程碑相关联。通常,里程碑就是某个版本。可以从 Issue 页面点击看到项目的里程碑,或者直接输入 http://github.com/ + 项目名 + /milestones 跳转对应页面。下图展示了 Apache Pulsar 目前正要发布的 2.11.0 版本的里程碑,Release Manager 会从这个面板里重点关注仍然 open 的 issue 和 PR 并推动解决,尽数解决后着手打包发布新版本。

Apache Pulsar 里程碑

第三个,项目管理的主要对象有哪些?

前面两个问题里提到的版本发布是主要的迭代流程。不过,项目管理的对象更多还是关注到人及人的活动上。观察社群成员及其活动的维度,方式前面全面评估圈选出的候选人的内容基本重叠。唯一需要补充的是在不知道应该看谁的情况下找到高效开发者的方式。

GitHub 仓库本身只提供 issue 和 PR 维度的筛选和排序,Contributions 页面是少有的排序参与者的页面。我们可以从讨论度高的 issue 里找到潜在的高效开发者。以 comments 数量为指标找到最热烈的 issue 可以直接从 Issues 页面点击 Sort 按钮选择 Most commented 排序来实现。

Most commented

我个人比较喜欢的方式,是找到被 requested review 最多的开发者,这些人通常是社群成员公认的专家和领袖。另一个维度是按照年份或月份罗列出某个项目或项目群上最活跃的开发者,以此来描绘出社群生产力的变迁。这些指标就需要额外的开发工作了,我会在闲暇时间逐步在开源小镇的社群看板页面一个个开发和发布。

理解开发流程

还有部分人力资源的从业者对技术研发的流程有着浓厚的兴趣,不仅希望知道项目层面的管理方法,还想知道研发的日常开发流程。

要想了解研发的日常开发流程,最好的参考资料就是开源项目的开发者指南。

这些手册都包含了项目开发者所需要了解的开发流程和最佳实践。当然,本文是面向人力资源的科普,不会展开里面的所有内容。

首先,简要介绍一下开发流程的分类。

开发流程不只是一个笼统的概念,我编写 TiDB 开发者指南的时候特地分门别类的解释。

  1. 报告缺陷。流程是发现缺陷的社群成员首先查找是否有同类问题,有则在相同 issue 下报告新案例,没有则重新创建一个。内容包括环境配置,软件和系统版本,复现步骤和预期结果。社群成员共同定位问题,如果不是代码缺陷而是使用问题或理解问题,则直接关闭议题。否则,提交补丁修复后关闭。
  2. 议题分类。如果项目参与者众多,每天都有复数的 issue 提交,分类这些 issue 也将成为一个独立的流程。我所知道的最佳实践是按照议题类型和模块这两个维度分出一系列的标签,分类者为议题打上对应的标签,并处理一些简单的情况。比如,要求提交者补充必要信息,或者针对已知的 non-issue 直接关闭。
  3. 提交代码。软件说到底是由代码组成,编译而来的。通常所理解的开发流程也是提交代码补丁和评审合并的流程。一般来说,代码补丁的描述里要说清楚解决了什么问题,做了哪些具体的修改。成熟项目一般还会要求补丁作者在改动关键路径或用户接口时明确提出,以及回答是否为代码变更添加测试和文档,如果没有,为什么的问题。
  4. 评审代码。提交代码的作者当然需要先做一个自我评审,尽量避免补丁当中包含初级错误,浪费评审人的时间。成熟项目一般要求两个以上有提交权限的人评审过补丁以后才能合并。评审代码总的应该以包容的心态提出建设性的意见,积极鼓励作者做得好的地方。如果补丁不能达到要求或者方向错误,则应该果断拒绝合并。TiDB 开发者指南当中评审代码一节值得一读。
  5. 提出议案。对于重大变更,许多开源项目会要求开发者提出包括背景动机、设计方案、实现方案和相关工作的正式提案。这种统称 RFC (Request for Comments) 的形式可以认为是提交代码的进化版。大多数议案最终还是要靠代码实现,只不过对于重大变更,社群维护者希望明确议案的来龙去脉并正式公告所有社群成员,充分收集意见后由维护者投票决议。决议通过以后,实现过程基本是重复多次提交代码和评审代码。
  6. 撰写文档。对于文档即代码的项目来说,这在流程上跟提交代码没什么区别。只是需要掌握的领域知识略有不同。

这其中,我想着重强调的是对背景动机的说明。

不少开源社群的新成员容易上来就怼一个补丁,PR description 放空或者模板一个字不改。我作为维护者看到这种补丁,尤其如果作者素未谋面,基本没有兴趣 review 补丁的内容。

如同我在上一篇文章《饱和沟通:开源社群的消息传递准则》里讲的,在信息爆炸的时代,大部分人都会先判断这件事情是否与自己有关,是否应该付出时间了解细节。好的 PR description 能够让维护者快速理解提交补丁想解决的问题和补丁做了哪些实际的改动,往往看过描述以后,维护者心里对这个问题应该怎么解,具体的改动应该怎么做会有一个预期。Code review 的过程说白了就是这个预期和 PR 作者实际写出的补丁的一个对比:快速略过跟预期相同的部分,集中在不同的部分。如果只是方式不同效果一样,我一般不会要求改。如果 PR 作者的方式比我想的要精彩,我会忍不住夸赞。如果 PR 作者遗漏了部分内容或者逻辑出错,我再评论指出。

刚开始开发软件的时候,很容易把写代码当成主要的创造过程,在撰写议案的时候不厌其烦地说明如何代码级的实现某段逻辑。但是作为项目的维护者,其实更加关心的是为什么要做这些改动,做出改动的技术方向是怎么定的。很多局部看起来应该做的改动,放到全局可能有着其他原因导致它演化成今天的样子。只要方向决策对了,实现上的改进可以细水长流。很多时候代码变更并不能一次就做好,需要经过一系列细分的步骤逐渐逼近最优的实现。实际上,软件开发主要的创造过程是考虑要不要做某个改动,改动的方向应该怎么定。至于代码的实现,很多时候是个体力活。当然,这不是说代码实现不重要,如果对代码实现的最终过程不熟悉,是不可能在方向决策上做出正确判断的。

有人会问,背景动机这么重要,那么它应该包含在流程的哪个环节里呢?

其实,这个问题同样没办法武断地回答。从最终的结果倒推,对软件施加影响的每一个 PR 都需要讲清楚动机。有些 PR 顾名思义,动机就是“如题”,比如 fix typo 或者升级版本以解决安全缺陷,这样也就够了,不必为了形式主义再弄出些什么别的流程来。其他 PR 在 description 里也都应该说明动机。其中,有些 PR 是一个大的议案的一部分,或者先提出 issue 说明 enhancement 的背景和方法,再有对应的 PR 提交。这种情况下议案或 issue 当中应该包含背景动机的说明,PR 针对相应解决的部分做简述,或者引用前者的表述即可。对于 PR 是修复 BUG 的,动机显而易见,不过修复 BUG 而已。

最后,常说没有文档的代码是只写不可读的,那么文档和代码的关系是怎么样的呢?

原始的问题是“怎么看代码对应的文档,还是应该是看文档对应的代码”。要讲清楚这里面的对应关系,还得对文档这个笼统的概念做分类讨论。

用户文档通常分成概念(Concept)、任务(Task)和参考(Reference)。参考文档主要由 API 文档构成。API 文档和软件代码大抵是一一对应的,代码里有什么接口,API 文档里也就解释什么接口。实际上,很多 API 文档是直接从接口注释上生成出来的。任务文档主要解决的是 how to do x with y 的问题,典型例子是如何在 Python 程序里连接 Pulsar 集群,进而如何生产、消费消息,如何配置鉴权信息,如何关闭攒批发送功能等等。任务文档一般在逻辑上对应一个或多个模块,如果读者具备区分功能模块的知识,通常也能反向找到模块对应的任务文档。不过,如果文档结构组织混乱,任务散落在各个分类下,就很难找了。概念文档基本不再对应到具体的代码,而是对软件领域核心概念的解释。比如 Pulsar 的概念文档主要包括系统架构图,消息队列基本对象的解释,消息生产和消费的关键语义的说明等等。

开发文档除了上面提到的通用流程文档,通常还包括对代码模块划分和各模块职责的解释,这里的模块和代码实际的模块结构是一一对应的。一般来说,只有想要了解系统运行的原理,参与到某个具体模块的开发的成员才会需要阅读开发文档。开发文档和代码的关系也是最紧密的。

对于用户来说,绝大部分情况下他们并不关心“对应的代码”是怎么回事,更多关注到如何解决眼前的使用问题。只要使用体验流畅,对应的代码爱怎么样就怎么样。


限于篇幅,本文到此为止。前半部分面向招聘 HR 的猎头,后半部分面向 HRBP、绩效管理和培训专员,业务团队也不妨一读。如果有意犹未尽之处,或者有其他开源和软件工程相关的问题,欢迎向我提问。

基于 ClickHouse 的 GitHub 事件数据库

缘起

ClickHouse 社群指标模型》一文里提到了 ClickHouse 社群基于自己的软件 ClickHouse 制作社群指标的探索。由于遇到了公开数据集表模式缺列,查询执行内存限制,以及数据库只读模式限制等问题,我在过去一周里试着按照 ClickHouse 官方博客的介绍,搭建起了一个属于自有的基于 ClickHouse 的 GitHub 事件数据库。

简单介绍下结果,自建数据集确实解决了上面列举的三个问题。然而,我选择的 16 核 64 GB 内存版本实例在查询性能上还不如 Playground 的性能,只是内存占用大的聚合查询 Playground 可能由于 Quota 限制无法执行。另外,我选择的阿里云上的 ClickHouse 数据库在追上游版本和一些使用体验上还是有所欠缺。最后,日常数据同步的脚本可以在 korandoru/github-adventurer 取用。

当然,我会去做这件事,还有一个不可忽视的原因是我所在的公司本身商业模式就是云上售卖数据处理服务,我希望能够基于云上的服务搭建自己的业务,体验这个流程可能遇到的各种问题。因此,本文不是一个指导手册或者技术指南,而是实现过程中每个环节的杂谈。

Metabase 连接 ClickHouse 数据库

从做这个数据集的出发点开始谈起,ClickHouse 官方文档里有详细的介绍如何连接 Metabase 的文档。然而,实操过程中碰到了两大问题。

  1. 前面提到的权限问题。Metabase 的 ClickHouse Driver 会向服务器发送设置查询参数的请求,由于 Playground 的服务器是 READONLY=1 的配置,因此 Metabase 无法连接。

这个问题是我陆陆续续花了一周时间准备自建数据集的出发点。技术上要么是 Driver 本身避免对这类语句的使用,因为在查询里带上查询相关的 SETTINGS 还是可以的;要么是 Playground 自己开启 READONLY=2 来允许这类查询。我也在上游提出了请求,这个确实只能由数据库管理员评估操作。

Is it possible to set read_only=2 for playground dataset?

  1. Metabase 的插件问题。上一点提到,Metabase 想要连接 ClickHouse 是需要专门的 Driver 的。metabase-clickhouse-driver 是由社群成员提供的,而 Metabase 云服务只支持官方的插件,这就导致哪怕我想花钱采购 Metabase 云服务,也因为无法连接数据库只能作罢。

这其实侧面揭示了云服务的一个缺点,也就是用户的选择权实际上被云厂商所剥夺,云厂商提供什么能力,用户就只能用什么能力。回到中台战略的年代,那些得不到中台关照的小型业务往往只能自己挣扎求存。

面对这种形势,一方面是凸显出云中立的技术厂商的价值,以及原厂能够最大限度地提供新版支持和旧版兼容的优势,另一方面也让我想起了之前看过的一篇文章 Local-first software 里面提出的“你拥有自己的数据,而不是云厂商”的理念。

诚然,云服务能够在很多场景下避免投入开发的成本,也能提供相对优质的服务。但是如果你真的很看重拥有自己的数据,或者对于核心数据,不妨考虑一下数据所有权的问题。当然,反过来说,到底自己能不能做得比云厂商更可靠,也是需要慎重斟酌的,在特定的核心领域里做出投入重点保障,可能也是必要的成本。

话说回来,最终我是用了本地的 Metabase 实例 + 配置 Driver 跑起来了整体应用。借助 Metabase 的 BI 能力,探索出了诸如分时最活跃的参与者这样的一些指标。不过我的可视化功底非常差,做出来的图表不好意思见人。如果实在想看,可以打开这条推文

购买云上 ClickHouse 数据库实例

没有找到御三家的 ClickHouse 服务,国内云厂商倒是不少。由于其他云厂商不允许外网访问,看起来技术支持也非常值得怀疑,于是选择了阿里云 ClickHouse 社区兼容版

总的来说,还算能用,也确实解决了开篇提出的三个问题,在 ClickHouse.com 出云服务并且试用稳定之前,可能还是会勉强续费。实际使用过程里有这么些问题和体验。

  1. 工单客服还是不错的。

大周末的值班,跟我这种要命的夜间生物一起排查问题…我绝对不鼓励加班,过程里也表达了对同行的理解。只能说做服务业的,大家都不容易。整体解决问题的速度和能力值得赞同。

  1. 外网访问需要技巧绕过。

由于众所周知的原因,本地机器 IP 不固定,服务部署的环境 IP 也不固定。我也不需要对这份数据集做特别极致的安全保护,于是尝试允许任意 IP 鉴权访问。不过,阿里云的产品显然有不一样的想法。它禁止了 0.0.0.0 的配置,又把 127.0.0.1 映射成仅允许本地访问。

不过广大人民群众的智慧是可靠的,这条推文里 @ImperiusDs 大佬想出了 127.0.0.1/1 的绕过方案,真是个天才。我希望阿里云不要不识好歹把这个方案也禁了,那我只能提前放弃续费了。

  1. 内核版本不足。

众所周知,ClickHouse 冲版本非常快,以至于 Yandex 当初自己都跟不上上游版本。或许业务对版本的要求是稳定就行,但是我只是一个个人业务,版本不足会带来一些实际的问题。

第一个,client 每次链接的时候都会有 warning 提示。这个还算好的了,只是比较烦人。

第二个,无法使用时间窗口等新功能。有些分析还是能用上会比较好,不是不能接受,稍微麻烦点。

第三个,不能支持跨域访问,这个就要命了。上游在去年底的时候才以 Add CORS support 解决了这个问题。没有跨域访问支持,我在制作开源小镇网站的时候就没办法用自己的数据集了。可以说为了解决 Metabase 连接,我要用自己的数据集,网站上固化 Dashboard 展示,又得换回 Playground 的数据源,一来一去让我感觉花的钱真的是血亏。

为此,一方面我给阿里云提了工单,另一方面给 ClickHouse 上游也提了我三个具体 blocker 的问题清单,希望两者之间有一个能够解决问题,我就切到能解决问题的那一方去。自己维护一个 ClickHouse 服务器还是太要命了,不予考虑。

最后介绍一下价格。不得不说云上的存储是真的便宜,扩容了 300GB 的存储每个月只多花几十块钱。目前的配置是单机 16 核 64GB 内存 + 500GB 云盘,每个月两千多块钱。这还是一笔不小的开销,目前的计划是利用 Metabase 尽量探索出有价值的指标,在网站上直接查 Playground 的数据出图表,一段时间后废弃数据集算逑。

GitHub 事件全量数据概览

ClickHouse 官方博客写成的时候,数据是从 2011 年到 2020 年,压缩前数据集大概 1.2TB 大小,压缩后导出文件不到 100GB 大小。压缩比还是很夸张的。

我制作的数据集从 2015 年到 2022 年实时更新,总事件数 47 亿条,运行时占用磁盘空间 400GB 左右,原数据量没有记录,应该也是几个 TB 大小。

很明显,GitHub 的增长是飞快的,每年事件数也呈明显上升趋势。实际下载数据时候也能感受到逐年向前的数据下载压力。

Count Events by Year

由于 GitHub Events 只有公开仓库的数据,所以 public 之前私下开发的活动是不被记录的。此外,大家都知道 GitHub 的可靠性非常感人,实际上有一些时间段的数据是缺失的。

  • 2016-01-28 01:00:00 ~ 02:00:00 数据缺失
  • 2016-10-21 18:00:00 ~ 19:00:00 数据缺失
  • 2018-10-21 23:00:00 ~ 2018-10-22 02:00:00 数据缺失
  • 2019-05-08 12:00:00 ~ 14:00:00 数据缺失
  • 2019-09-12 08:00:00 ~ 2019-09-13 06:00:00 数据缺失
  • 2020-03-05 22:00:00 ~ 23:00:00 数据缺失
  • 2020-06-10 12:00:00 ~ 22:00:00 数据缺失
  • 2020-08-21 09:00:00 ~ 2020-08-23 16:00:00 数据缺失
  • 2020-10-30 损坏一条数据 id=14032425374
  • 2021-08-25 17:00:00 ~ 2020-08-27 23:00:00 数据缺失
  • 2021-09-11 损坏一条数据 id=17943409164
  • 2021-10-22 05:00:00 ~ 23:00:00 数据缺失
  • 2021-10-23 02:00:00 ~ 23:00:00 数据缺失
  • 2021-10-24 03:00:00 ~ 23:00:00 数据缺失
  • 2021-10-26 00:00:00 ~ 2021-10-29 18:00:00 数据缺失

除了这些整段的数据缺失以外,部分数据缺失也是可能的。CNCF 的 devstats 项目有一系列的补偿逻辑来修复数据,这里不做展开。

总的来说,GitHub 事件数据集没有单个事务级别的完整性,也就是因果性无法完全保证,只适合做一些粗略的倾向分析和大数统计,无法做特别精确的因果分析。

GitHub 事件的数据模型

ClickHouse 官方博客 How to choose the structure for the data? 章节已经讲清楚了数据模型的选型。前文提到的 devstats 和 PingCAP 的 OSSInsight 都是关系型数据库打底,基本是多个具体表分发处理不同事件类型,查询时走 JOIN 查询来出结果的。ClickHouse 作为列存数据库,则更加倾向于一张大表多个列储存所有数据,也是因为这种模式,才能做到数据极高的压缩比和查询时的过滤效率。

我所采取的建表模式和官方博客的模式略有不同,这也是我选择自建数据集的关键原因之一。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
CREATE TABLE default.github_events
(
`file_time` DateTime,
`event_id` UInt64,
`actor_id` UInt64,
`repo_id` UInt64,
`event_type` Enum8('CommitCommentEvent' = 1, 'CreateEvent' = 2, 'DeleteEvent' = 3, 'ForkEvent' = 4, 'GollumEvent' = 5, 'IssueCommentEvent' = 6, 'IssuesEvent' = 7, 'MemberEvent' = 8, 'PublicEvent' = 9, 'PullRequestEvent' = 10, 'PullRequestReviewCommentEvent' = 11, 'PushEvent' = 12, 'ReleaseEvent' = 13, 'SponsorshipEvent' = 14, 'WatchEvent' = 15, 'GistEvent' = 16, 'FollowEvent' = 17, 'DownloadEvent' = 18, 'PullRequestReviewEvent' = 19, 'ForkApplyEvent' = 20, 'Event' = 21, 'TeamAddEvent' = 22),
`actor_login` LowCardinality(String),
`repo_name` LowCardinality(String),
`created_at` DateTime,
`updated_at` DateTime,
`action` Enum8('none' = 0, 'created' = 1, 'added' = 2, 'edited' = 3, 'deleted' = 4, 'opened' = 5, 'closed' = 6, 'reopened' = 7, 'assigned' = 8, 'unassigned' = 9, 'labeled' = 10, 'unlabeled' = 11, 'review_requested' = 12, 'review_request_removed' = 13, 'synchronize' = 14, 'started' = 15, 'published' = 16, 'update' = 17, 'create' = 18, 'fork' = 19, 'merged' = 20),
`comment_id` UInt64,
`body` String,
`path` String,
`position` Int32,
`line` Int32,
`ref` LowCardinality(String),
`ref_type` Enum8('none' = 0, 'branch' = 1, 'tag' = 2, 'repository' = 3, 'unknown' = 4),
`creator_user_login` LowCardinality(String),
`number` UInt32,
`title` String,
`labels` Array(LowCardinality(String)),
`state` Enum8('none' = 0, 'open' = 1, 'closed' = 2),
`locked` UInt8,
`assignee` LowCardinality(String),
`assignees` Array(LowCardinality(String)),
`comments` UInt32,
`author_association` Enum8('NONE' = 0, 'CONTRIBUTOR' = 1, 'OWNER' = 2, 'COLLABORATOR' = 3, 'MEMBER' = 4, 'MANNEQUIN' = 5),
`closed_at` DateTime,
`merged_at` DateTime,
`merge_commit_sha` String,
`requested_reviewers` Array(LowCardinality(String)),
`requested_teams` Array(LowCardinality(String)),
`head_ref` LowCardinality(String),
`head_sha` String,
`base_ref` LowCardinality(String),
`base_sha` String,
`merged` UInt8,
`mergeable` UInt8,
`rebaseable` UInt8,
`mergeable_state` Enum8('unknown' = 0, 'dirty' = 1, 'clean' = 2, 'unstable' = 3, 'draft' = 4),
`merged_by` LowCardinality(String),
`review_comments` UInt32,
`maintainer_can_modify` UInt8,
`commits` UInt32,
`additions` UInt32,
`deletions` UInt32,
`changed_files` UInt32,
`diff_hunk` String,
`original_position` UInt32,
`commit_id` String,
`original_commit_id` String,
`push_size` UInt32,
`push_distinct_size` UInt32,
`member_login` LowCardinality(String),
`release_tag_name` String,
`release_name` String,
`review_state` Enum8('none' = 0, 'approved' = 1, 'changes_requested' = 2, 'commented' = 3, 'dismissed' = 4, 'pending' = 5)
)
ENGINE = ReplacingMergeTree
ORDER BY (event_type, repo_name, created_at, event_id)

可以看到,表格列添加了 event_id / actor_id / repo_id 三列,这是因为需要 event_id 去重,后两者可以较好的应对用户和仓库改名的情况。

同时,表引擎选择的是 ReplacingMergeTree 引擎而非 MergeTree 引擎,并多制定了 event_id 作为排序键,同时也是 ReplacingMergeTree 的去重基准。

数据集的具体制作

可以在 korandoru/github-adventurer GitHub 仓库上获取制作数据集的所有相关脚本。

目前,我通过 GitHub Actions 每个小时增量从 GHArchive 拉取新数据插入到数据库里。对于全量的导入,只需要把 download.sh 的逻辑替换成全量下载即可。由于没有云服务器可以直接用,购买云服务器和带宽我血亏了几百块钱以后发现还不如本地机器来的靠谱,所以最后是用自己的机器分批次导入全量数据的。GHArchive 数据下载不用梯子就可以访问,速度也很快。

增量数据下载上游的脚本是全量存储了所有 .json.gz 的原始文件,所以可以直接以当前时间为基准框出前后十二个小时的数据集文件名后过滤。我没有那么大的存储空间,只能从当前最新的数据往后连续数一天了。

具体的脚本逻辑不逐行讨论,只说几个值得一提的点。

  1. 导入 GitHub 事件数据的过程确实就是一个 ETL 的过程,为此我直接把分阶段的处理文件叫做 extract / transform / (up)load 了。

这当然不止是因为强迫症。全量导入数据的时候,下载完成以后从 flatten.sh 入口驱动批量导入,由于网路和数据质量等原因有些数据可能导入失败,这个时候就只需要从错误输出里找到失败的数据,传参给 transform.sh 重新导入即可。

这里也可以以小见大看出软件开发过程里模块化的好处来,能够分离关注点避免同时处理多个事情对人的生理挑战进而引入缺陷,每个阶段的逻辑可以单独复用而无需重复编写。

  1. 处理 JSON 数据时用的是 jq 这个实用工具,强烈推荐。

不过 jq 处理的速度也不算快。数据导入的时候我按照本地核数做数据并发,10 个并发的情况下最后数据库导入效率也就每秒 15MB 左右,升配了也上不去,属于本地处理的瓶颈了。

  1. ClickHouse 对数据输入和输出的格式支持做得非常到位。

JSONCompactEachRow 输入和 HTTP 的 FORMAT JSON 输出,跟后端程序还有网页对接起来不要太爽。导入数据再也不用跟复杂的 INSERT 语句打架了。

  1. dotenv 对统一开发环境和生产环境部署有很大的帮助。

可以看到,自建数据集需要配置自己购买的数据库实例。很多应用都会包含敏感的配置信息,过去都是八仙过海处理不同环境下的配置。无论是不同的处理逻辑,还是命令行传参,都不是那么舒服。

如今,dotenv 越来越多得到用户的青睐,不是没有道理的。因为理念足够简单,要么从 .env 文件读取配置,要么从环境变量读取,所以绝大部分集成开发环境和语言都支持和 dotenv 标准协同工作。本地走一个 .env 文件并从 VCS 里 ignore 掉,生产环境配置环境变量,就能解决逻辑不一致和命令行传参仍然有泄露风险的问题。

当然,dotenv 对复杂数据类型的支持一般。对于复杂的配置需求,还是需要专门的配置系统来处理。但是连接到配置系统的地址和用户信息又可以用 dotenv 管起来。总之和 jq 一样,属于新一代生产力利器。

还有一些 bash shell 和 GitHub 的小技巧就不一一罗列了,从 korandoru/github-adventurer 的源码里都能看出端倪。

回顾一下这轮工作的成果,自建数据集确实解决了一开始列举的三个问题。但是阿里云版 ClickHouse 并不能解决我遇到的所有问题,我现在需要同时连接两个数据库完成 Metabase BI 探索和网页端查询固化的工作。如果 ClickHouse.com 上线了云服务,我会再尝试一下。另外,现在对云服务的使用也算一回生二回熟了,如果有精力我可以看看用 cockroachdb 平替 PostgreSQL 以每种 event 一张表的形式来组织数据。目前至少也有一些用户详情的元数据想存到一个关系型数据库上的需求。

❌