放弃 Rust 选择 Zig,Xata 团队推出 pgzx —— 计划使用 Zig 开发基于 PG 的分布式数据库

news2024/11/14 14:39:39

Summary

Xata 公司在基于 PostgresSQL 开发自己的分布式数据库,出于 Zig 和 C 语言以及 PostgreSQL 的 API 有更好的互操作性的考虑,他们选择了 Zig 而非当红炸子鸡语言 Rust。他们的博客文章中对 pgzx 进行了介绍。让我们来看下他们对 Zig 和 Rust 语言的对比,以及 pgzx —— 一个支持用 Zig 语言来开发 PG 插件的框架。

Xata 是一个基于 PostgreSQL 的 Serverless 数据平台,在 PostgreSQL 之上提供全文搜索、向量相似性搜索和文件/图像等小文件存储等功能。根据官网的介绍,Xata 希望从根本上简化开发人员处理数据的方式 —— “数据库不仅仅可以在表中存储行和列,我们希望提供一站式的多模存储,并提供一个可与您喜爱的工具配合使用的互联数据平台。”

3 月 21 日,在 Xata 的发布周,Xata 的工程师发布了 pgzx 。pgzx,是一个使用 Zig 编程语言创建 Postgres 扩展的框架,提供一组基础库(例如错误处理、内存分配器、Wrapper 包装)以及构建和开发环境,简化了与 Postgres 代码库的集成。在介绍文章中,项目的主要开发者,Tudor Golubenco 和 Steffen Siering 介绍了该拓展的有趣的功能,以及他们为什么选择 Zig 来进行开发。相对于大家已经熟悉的 pgrx(基于 Rust 的创建 Postgres 扩展的框架),它有什么优势?

用 Zig 语言开发类似于 Citus 的分布式数据库

Xata 的工程师一直在做一个新的 Postgres 项目,该项目还没有真正的名称,内部称其为 “Xata 的分布式 Postgres 行动”(Xata’s take on distributed Postgres)。这个项目还处于早期阶段,但是已经有了开源计划。它与 Citus 有点相似,不过,基于过去几年运行 Xata 所学到的知识,团队做了一些不同的选择。

在项目之初,Xata 团队考虑了三种潜在的实现方向:

  1. 作为外部代理,类似于 Vitess 对 MySQL 的处理方式。
  2. 作为一个 Postgres 扩展,类似于 Citus。
  3. 作为 Postgres 的一个分支,类似于 Greenplum。

对第一种选项,他们显然更有经验,因为这就是目前的 Xata 代理的工作方式,所以也就很自然的倾向于这种方式。可以使用 Postgres 的网络协议,解析传入的查询并深入理解它们,并且通过两阶段提交创建分布式事务。然而,经过思考,他们发现利用现有的 Postgres 代码将会带来更多的选择,最好能复用那些非常复杂的部分。考虑到项目的长期愿景,加上也不想维护一个分支,最终决定做一个类似于 Citus 的 Postgres 拓展。

在编程语言的选择中,他们则选择了 Zig,排除了 C 和现在非常流行的 Rust。Xata 的工程师在 HackerNews 论坛上表示,“使用 Zig 的原因之一是我们可以直接复用 C API。

Zig 的主要优势包括:

  • 它几乎可以直接调用 Postgres 的 API,这样我们拥有与使用 C 相同的能力。
  • 它比 C 提供更多的内存安全性,更具表现力,也更有趣。
  • 它与 Postgres 代码库非常匹配,例如在内存管理和字符串处理方面。

决定了方向和语言之后,pgzx 也就应运而生了。就像用 Rust 开发 PG 插件产生了 pgrx 项目,Xata 也需要一个 Zig 版本。Zig 和 pgzx 不一定适合去开发所有的 Postgres 扩展,但他们认为它是将来这个类似于 Citus 项目的最合理选择。

介绍一下 Zig

Zig 在其网站上被描述为一种通用的编程语言和工具链,用于维护健壮、优化和可重用的软件。Zig 仍然是一种新的语言(pre 1.0),但它在系统编程社区中已经逐渐开始流行,并有网友称其为 “最赚钱的编程语言”。Zig 简化了很多东西,也因此被戏称 Rust --, 但是简化不代表弱,Zig 为 C 语言提供了一个很不错的解决方案,提供更安全的内存管理,编译时计算(comptime)以及丰富的标准库。

使用 Zig 进行 PostgreSQL 开发的一个主要原因是它能够与 C 代码进行互操作。Zig 支持 C ABI,可以与 C 指针和数据类型兼容,包括 NULL 结束的字符串,并且甚至可以将 C 头文件转换为 Zig 代码。Zig 将 C 语言的宏自动转换为 Zig 代码的功能虽然还不是很完美,但仍然很有帮助。

pgzx 这个项目,由于其使用了 Zig 语言,也有了一些有趣的新特性。并且由于语言的选择也带来了很广泛的讨论度。

pgzx 的简介

Zig 可以调用任何 C 函数并将 C 的宏转换为内联 Zig 函数,您可以按照与开发一个 PG 的 C 扩展相同的步骤用 Zig 来编写 Postgres 扩展,不需要任何其他工具。但是,pgzx 可以提供开发环境、一组用于 Postgres API 的基础库和 Wrapper 包装、常见错误处理等,这样的框架显然可以简化使用 Zig 中编写 Postgres 扩展的流程。

示例

pgzx 目前有 2 个示例扩展。

  • char_count_zig, 一个很简单的扩展;
  • 以及 pg_audit_zig , 更复杂并且显示了 pgzx 的更多功能。

我们看一下 char_count_zig,它是一个功能上只比 “Hello, World!” 多一点点的 Postgres 扩展示例,添加一个函数来计算字符在字符串中出现的次数。

select char_count_zig('hi hii', 'i');

 char_count_zig
----------------
              3
(1 row)

虽然用 Zig 和 C 很相似,但 Zig 版本更简洁一些。一方面是因为 Zig 更具表现力,另外也是因为 pgzx 做了更多的工作。

注册的 SQL 函数接收序列化的参数,并且需要一些代码来进行反序列化。在 C 中,可以用 G_GETARG_* 宏来完成此操作,但是使用 pgzx,您只需将它们作为已反序列化的普通参数接收即可。如何?通过使用 comptime 函数在编译时生成必要的样板代码。如果您好奇,请查看 pgCall 函数,这是一个很好的例子,展示了 Zig 在comptime 执行的强大功能。

PG_MODULE_MAGICPG_FUNCTION_INFO_V1 函数是 comptime 使用的第二个示例。它们导出 Postgres 所需的符号,将其识别为扩展并将该函数注册为 SQL 函数。在这种情况下,comptime 的行为与对应的C语言宏非常相似。

运行时内存安全

如果你仔细看了上面的代码,就会发现它有个 bug。它检查了 target_char 不应该超过 1 个字符,但它没有检查它是否为 0 个字符。之后,代码访问 target_char[0] 时,如果字符串为空字符串,就会出现越界访问错误。我们故意留下了这个bug,这样可以看到当扩展中出现这种错误时会怎么样。

使用以下 SQL 触发此错误:

select char_count_zig('hi hii', '');

返回信息:

server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

在 C 代码中,此类错误可能会触发 segmentfault 错误甚至安全漏洞。使用 char_count_zig 扩展尝试此操作,Postgres 进程仍然崩溃(但不是整个服务器受影响,只有提供连接的进程),但是检查日志,您将看到如下错误消息:

thread 70501513 panic: index out of bounds: index 0, len 0
/Users/tsg/src/xataio/pgzx/examples/char_count_zig/src/main.zig:21:32: 0x103aaedff in char_count_zig (char_count_zig)
        if (char == target_char[0]) {
                               ^
/Users/tsg/src/xataio/pgzx/src/pgzx/fmgr.zig:95:5: 0x103aaf20f in call (char_count_zig)
    const value = @call(.no_async, impl, callArgs) catch |e| elog.throwAsPostgresError(src, e);
    ^
???:?:?: 0x10316045b in _ExecInterpExpr (???)
???:?:?: 0x10315fbef in _ExecInterpExprStillValid (???)
???:?:?: 0x10326ceef in _evaluate_expr (???)
???:?:?: 0x10326da67 in _simplify_function (???)
???:?:?: 0x10326bacf in _eval_const_expressions_mutator (???)

它准确指出了错误发生的位置!发生这种情况是因为 Zig 根据 Build Mode(Zig 有四种 Build Mode,Debug (default),ReleaseFast,ReleaseSafe 和 ReleaseSmall)进行运行时检查。例如,ReleaseSafe 模式会牺牲一些性能来换取更多安全检查。

请注意,这个 stacktrace 很完整,因为错误出现在 Zig 代码中。在构建 Postgres 扩展时,经常需要调用 Postgres API,如果使用不正确,仍然会出现 segmentfault 错误。

内存管理

Postgres 使用分配器 arena 来管理内存。在 Postgres 源代码中,这些 arena 被称为 memory contexts 。在一个“ contexts ”中分配的内存可以一次性释放(比如,当查询执行完成的时候),这显著简化了内存管理,因为你只需要跟踪 “contexts”,而不是单个分配。Contexts 还是分层的,因此你可以创建一个 contexts 作为另一个 contexts 的子节点,当父节点的 contexts 被释放时,所有子节点也将被释放。这样内存泄漏就几乎不可能发生。

内存 contexts 的另一个优点是它们改善了内存监控,因为 contexts 有名称,你可以看到每个 contexts 使用了多少内存。这对于调试大内存场景是很有用的。 这种使用 arena/contexts 分配器的模型恰好非常适用于 Zig。 因为 Zig 的使用习惯是让所有分配内存的函数/对象接收一个分配器作为参数。这样,分配内存的行为会更加显式,而且适合使用自定义分配器的地方。pgzx 在 Postgres 的 contexts 外面又包装了一层,这样定义的自定义内存分配器更适合被 Zig 代码调用。

以下是一个示例,创建了一个新的 contexts 作为当前 contexts 的子节点,并获取了该 contexts 的分配器:

var memctx = try pgzx.mem.createAllocSetContext("zig_context", .{ .parent = pg.CurrentMemoryContext });
const allocator = memctx.allocator();

错误处理

在更复杂的扩展中,你很可能需要了解 Postgres API 的错误处理机制。Postgres 通过 setjmp/longjmp 实现了 C 中的 "异常"处理,并提供了一组宏来抛出和捕获它们(PG_TRY/PG_CATCH)。

问题在于长跳转可能会绕过 Zig 的控制流,比如说,errdefer 块可能没有被执行。这说明如果你的扩展调用了 Postgres API,这些 API 可能会抛出错误,长跳转可能会跳过你的 defererrdefer 块!

幸运的是,pgzx 此时可以发挥作用。它提供了一组函数,允许你捕获 Postgres 的异常并将它们转换为 Zig 的错误。例如:

var errctx = pgzx.err.Context.init();
defer errctx.deinit();
if (errctx.pg_try()) {
    // Call Postgres C functions.
} else {
    return errctx.errorValue();
}

开发环境

pgzx 附带一个基于 Nix flakes 的开发环境,可以用于开发扩展和 pgzx 本身。它还附带一个项目模板,您可以使用该模板在新存储库中设置此环境。

请安装 Nix,然后运行:

mkdir my_extension
cd my_extension
nix flake init -t github:xataio/pgzx

然后加载 nix shell:

nix develop

开发环境包括许多命令,这些命令可以用于在开发环境中重新定位 Postgres 二进制文件、启动服务器等。该模板还附带一个最小的扩展和一个包含一些常见任务的 build.zig 文件。请参阅模板 README 文件,了解如何从此时开始构建扩展。

单元测试

Postgres 扩展通常使用名为 pg_regress 的工具进行测试。 pgzx 也一样,只需调用 zig build pg_regress 即可。

但我们也想进行单元测试。这有点棘手,因为测试需要在 Postgres 实例的 contexts 中编译和运行。否则,就无法与 Postgres 的 API 交互。

为了解决这个问题,pgzx 注册了一个定制的 run_tests 功能。可以通过 SQL 调用(SELECT run_tests()) 来运行单元测试。一个测试套件是一个以 test 开头的 Zig 结构体。

要注册一个测试套件,你通常会做类似以下的操作:

comptime {
    pgzx.testing.registerTests(@import("build_options").testfn, .{Tests});
}

registerTests 函数是 comptime 使用的另一个用例。它会迭代结构体的所有字段,并在 SQL 中调用 run_tests() 函数时生成运行测试的调用。

其他

本文涵盖了 pgzx 已发布的一些有趣的功能,但还 pgzx 还有更多功能,比如,Postgres 数据结构的 wrapper(列表、哈希表)、SPI、共享内存访问、连接管理等等…

现状和下一步计划

pgzx 现在还是 “alpha” 阶段。但如果您想要构建 Postgres 扩展并且想要使用 Zig,使用 pgzx 会容易得多。现有功能位于README.文件中。

另外,如果这篇博文激发了您对 Zig 的兴趣并且您想尝试一下,为什么不开发一个 Postgres 扩展呢?

既生瑜,何生亮?

几乎每个程序员都为自己喜爱的语言辩护。尽管 pgzx 本身的能力很引人注目,Xata团队对未来的计划也令人兴奋,但是最引发讨论和质疑的,无疑是其选择了 Zig 作为开发语言。在 HackerNews 社区的网友也大多都关于这一点。
图源 X @ThePrimeagen

有网友表示,“ Zig 是一门非常令人愉快的语言。 comptime 是一项巧妙的发明。它以相同的语言语法实现类型安全的元编程和简单的通用支持。对 u2、u3 或 u7 等子字节类型的本机支持使打包数据变得非常容易。 SIMD 上的原生向量支持使得 SIMD 级并行编程就像儿童游戏一样。这种语言看起来非常好。

也有人直接提出这个问题,难道 Rust 不是一个清楚的选择吗?Zig 跟 C 的互操作性看起来很抽象啊。对此,博文作者 Tudor 亲自下场,说,“Rust 很好啊,但是在 Zig 中,我们几乎可以直接使用任何东西,非常方便。”

还有的观点指出,“我还没有使用 pgzx,但内存管理可能更容易。 Zig 使用内存区域,Postgres 也是如此。如果一个可以直接映射到另一个,那将是一个big win。对于 Rust/pgrx(我已经广泛使用过),内存集成更加困难。有 pg 内存,有 Rust 内存,它们不一样,你必须玩游戏才能在它们之间传递数据。终生问题突然出现。 Rust 将来也许能够通过自定义分配器解决这个问题,但目前还没有实现。”

更有人直接说“当使用 Rust 处理复杂的数据结构时,经常需要编写不安全的代码(unsafe code)。Unsafe code 在 Rust 中使用起来要痛苦得多。一旦进入,无论如何你都是在不安全的地方,有人说 Zig 更方便。”

针对这些,你怎么看,欢迎加入社区跟我们一起讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1546249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创建多节点 k8s 集群

主机IP系统master192.168.2.15ubuntu20.04 x64 2C 4GWorker1192.168.2.16ubuntu20.04 x64 2C 4GWorker1192.168.2.18ubuntu20.04 x64 2C 4G 使用 iterm2 连接四台服务器 command shift i 同时操作 初始化配置 关闭防火墙 systemctl stop firewalld systemctl disable firewa…

Qt篇——Qt无法翻译tr()里面的字符串

最近遇到使用Qt语言家翻译功能时,ui界面中的中文都能够翻译成英文,但是tr("测试")这种动态设置给控件的中文,无法翻译(lang_English.ts文件中的翻译已经正确添加了tr()字符串的翻译)。 上网搜了很多资料&am…

Redis(十八)Redlock算法

文章目录 自研锁逻辑lock加锁关键逻辑 Redlock红锁算法自研锁存在的问题Redlock算法设计理念RedisonRedisson使用案例Redisson源码分析多机案例 自研锁逻辑 按照JUC里面java.util.concurrent.locks.Lock接规范编写 lock加锁关键逻辑 加锁的Lua脚本,通过redis里面的…

vue3+vite - 报错 import.meta.glob() can only accept string literals.(详细解决方案)

报错说明 在vue3+vite项目中,解决报错: [plugin:vite:import-analysis] import.meta.glob() can only accept string literals. 如果我们报错差不多,就可以完美搞定这个错误。 解决教程 这个错误,是因为

【笔记】MJ Prompt

参数 --chaos 10 or --c 10, 0-10, defalut 0 --quality 1 or --q, 0.25-1, defalut 1 --iw 2, 0.5-2, --stylize 100 or --s 100, 0-1000, defalut 100 --cref URL --cw 100, 0-100stylize 风格化,MJ不同的出图模式,有默认的艺术风格,该值…

TCP协议中的传输控制机制图文详解「重传机制」「流量控制」「拥塞控制」

目录 TCP重传机制 超时重传 快速重传 SACK 方法 Duplicate SACK TCP 流量控制 滑动窗口 累积确认 窗口大小由哪一方决定? 接收窗口和发送窗口的大小是相等的吗? 流量控制 窗口关闭的后果 糊涂窗口综合症 TCP拥塞处理 为什么要有拥塞控制呀,不…

LinkedIn账号为什么被封?被封后如何解决?

近期会有一些小伙伴说自己遇到了帐号无法登录的情况,其实出现领英帐号被封号(被限制登录)主要会有两类情况,今天就给大家分享一下如果被封该如何解决,强烈建议收藏。 在电脑领英官网或者手机领英APP上,输入领英帐号密码点击登录后…

力扣● 84.柱状图中最大的矩形

84.柱状图中最大的矩形 需要找到元素i的上一个更小的元素leftmin和下一个更小的元素rightmin,这样leftmin和rightmin之间的元素都比当前元素i更大,那么矩形的宽就是中间的这些元素:可以从leftmin1延伸到rightmin-1,长即为height[i…

rancher2.6部署

rancher2.6部署 1、准备环境镜像 2、部署3、密码获取密码设置新密码 4、设置语言5、导入已有集群 1、准备 环境 docker-ce-20.10.23-3.el8.x86_64.rpm以及依赖rpm kubernetes:v1.23.17 镜像 (rancher和k8s有个版本对应关系,rancher2.5就不…

走进redisson

这里作者将大家走进redisson,读完这篇相信加深你对redisson的获取锁,重入,超时,看门狗,发布订阅等原理和功能的理解。 本文将深入原理代码,给出每行代码的意义以及最后的效果,过程有些枯燥&…

Python 指南-最短路径(Dijkstra 算法):

Dijkstra 算法可在 Python 库 OSMNX 中实现,可用于查找两个位置之间按距离或时间加权的最短路径。该算法使用 OpenStreetMap (OSM) 网络来驾驶、步行或骑自行车,并在后台使用 Python 库 NETWORKX 查找路线。 编码练习 正如我提到的,我将做一…

MySQL数据库 @@transaction_isolation参数的查询及修改

在应用开发过程中,可能会检查mysql数据库初始化参数符合要求。 遇到这种情况就要进行相应的调整。 1、查询参数信息 select transaction_isolation; 2、找到配置文件 ,以window系统为例。 修改前先关闭MySQL数据库服务 对应需要修改的参数&#xff…

C语言看完我这篇最详细文件操作,你不会也得会!!!

1.使用文件 我们写的程序的数据是存储在电脑内存中,如果程序退出,内存就会被回收,数据就丢失,内存更具有一些实时性,等再次运行程序的数据的,数据就消失了,如果想要持久化的保存,可以…

LangChain-Chatchat

文章目录 关于 LangChain-Chatchat特性说明实现原理文档处理流程技术路线图(截止0.2.10) 使用 关于 LangChain-Chatchat Langchain-Chatchat(原Langchain-ChatGLM)基于 Langchain 与 ChatGLM 等语言模型的本地知识库问答。 gith…

[自研开源] 数据集成之分批传输 v0.7

开源地址:gitee | github 详细介绍:MyData 基于 Web API 的数据集成平台 部署文档:用 Docker 部署 MyData 使用手册:MyData 使用手册 试用体验:https://demo.mydata.work 交流Q群:430089673 介绍 本篇基于…

STM32F4x7标准库移植LWIP

项目背景 使用GD芯片的我们,都会去参考ST的代码。可是呢,有一个很大的问题就是,ST早就提供HAL库了,而目前GD还只有标准库。在移植LWIP的时候,会有很多不便。 好在天无绝人之路,找到了一份ST的官方例程&am…

java常用IO流功能——字符流和缓冲流概述

前言: 整理下学习笔记,打好基础,daydayup! 之前说了下了IO流的概念,并整理了字节流,有需要的可以看这篇 java常用应用程序编程接口(API)——IO流概述及字节流的使用 字符流 FileReader(文件字…

基于注意力机制和损坏特征掩蔽的遮挡人脸识别

Occluded Face Recognition Based on Attention Mechanism and Damaged Feature Masking 摘要 本文提出了一种基于注意力机制(BAM)和掩模生成器的新型遮挡人脸识别方法。在主干网络中嵌入BAM以提取更多可区分的特征,同时设计掩模生成器来清理…

媒介盒子揭秘0基础写好软文的秘诀

在流量见顶的传播环境下,品牌获取用户注意力的主要方式就在软文,然而有许多品牌在写软文时往往摸不着头脑,不知道怎么写,如何写才能使品牌有效曝光,今天媒介盒子就来和大家聊聊:0基础也能写好软文的秘诀&am…