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

news/2024/5/14 11:12:35

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 unexpectedlyThis probably means the server terminated abnormallybefore 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.mrgr.cn/p/42154505

相关文章

Qt消息机制和事件

Qt消息机制和事件 Qt消息机制和事件--2 事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出&…

IP组播基础

原理概述 IANA ( Internet Assigned Numbers Authority )将 IP 地址分成了 A 、 B 、 C 、 D 、 E5类,其中的 D 类为组播 IP 地址,范围是224.0.0.0~239.255.255.255。 一个 IP 报文,其目的地址如果是单播 IP 地址&#xff…

MLIR多层中间表示——用MLIR构建编译器(中)

1.3. 玩具语言IR方言 玩具语言方言:方言 在TableGen中声明性指定 def Toy_Dialect : Dialect {let summary = Toy IR Dialect;let description = [{这是对玩具语言方言的一个更长的描述...}];// 方言的命名空间.let name = toy;// 方言类定义所在的C++命名空间let cppNamespac…

Gitlab的流水线任务【实现每小时自动测试 dev分支的更新】

背景 在现代软件开发实践中,持续集成(Continuous Integration, CI)是确保代码质量和快速响应软件缺陷的关键策略。GitLab 提供了强大的 CI/CD 功能,允许开发者自动化测试和部署流程。本文将介绍如何设置 GitLab 流水线计划任务&a…

【iOS ARKit】3D文字

首先,3D场景中渲染的任何虚拟元素都必须具有网格(顶点及顶点间的拓扑关系),没有网格的元素无法利用GPU 进行渲染,因此,在3D 场景申渲染 3D文字时,文字也必须具有网格。在计算机系统中&#xff0…

OSCP靶场--pc

OSCP靶场–pc 考点(CVE-2022-35411[rpc漏洞chisel端口转发]) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV 192.168.178.210 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-28 04:07 EDT Nmap scan rep…

景联文科技高质量大模型训练数据汇总!

3月25日,2024年中国发展高层论坛年会上,国家数据局局长刘烈宏在“释放数据要素价值,助力可持续发展”的演讲中表示,中国10亿参数规模以上的大模型数量已超100个。 当前,国内AI大模型发展仍面临诸多困境。其中&#xff…

快速创建zookeeper集群

先说明,zookeeper集群的3个节点都放在同一个虚拟机(穷),所以搭建是一个伪集群,因为一个服务器挂机,所有节点都会停止。工作实际情况安装到三个服务器,并修改节点配置的ip地址即可(红…

itextPdf生成pdf简单示例

文章环境 jdk1.8&#xff0c;springboot2.6.13 POM依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version></dependency><dependency><groupId>com.ite…

C++多重继承与虚继承

多重继承的原理 多重继承(multiple inheritance)是指从多个直接基类中产生派生类的能力。 多重继承的派生类继承了所有父类的属性。 在面向对象的编程中&#xff0c;多重继承意味着一个类可以从多个父类继承属性和方法。 就像你有一杯混合果汁&#xff0c;它是由多种水果榨取…

Visio中存在问题的解决方法

公式缩放 mathtype公式在visio缩放之后&#xff0c;出现了变形。 解决方法&#xff1a;每次输入公式都通过 插入->对象->mathType Equation 新建一个公式。可以避免 注&#xff1a;网上有的说在word中使用mathtype编写公式&#xff0c;之后复制到visio中。 插入波形 选择…

python知识点总结(十)

python知识点总结十 1、装饰器的理解、并实现一个计时器记录执行性能&#xff0c;并且将执行结果写入日志文件中2、队列和栈的区别&#xff0c;并且用python实现3、设计实现遍历目录与子目录4、CPU处理进程最慢的情况通常发生在以下几种情况下&#xff1a;5、CPU处理线程最慢的…

C# 获取程序路径的几种方法及其区别【WPF】

遇到的问题 但最近发现一个问题:如果程序是由用户通过exe文件直接打开的,获取同目录下配置文件不会有问题;但如果程序是由第三方应用启动的,通过不同方式获取的“当前目录”会有不同。以下是常用的记住获取当前目的的方法: System.Environment.CurrentDirectory System.IO…

C语言分支循环语句详解

分支和循环语句是什么 在我们写程序的时候&#xff0c;总会遇到想一直循环执行某种语句的时候&#xff0c;这时候我们就要使用一种语句叫循环语句&#xff0c;或者带一些判断条件的语句&#xff0c;在C语言中提供了像这些的循环语句和分支语句 if else 语句 这是一种判断语句…

P8623 [蓝桥杯 2015 省 B] 移动距离 Python

[蓝桥杯 2015 省 B] 移动距离 题目描述 X 星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为 $1,2,3, \cdots $ 。 当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。 比如&#xff1a;当小区排号宽度为 6 6 6 时&#xff0c;开始情形如…

学习总结!

1.构造器&#xff1a; 构造器通常也叫构造方法、构造函数&#xff0c;构造器在每个项目中几乎无处不在。当你new一个对象时&#xff0c;就会调用构造器。构造器格式如下&#xff1a; [修饰符&#xff0c;比如public] 类名 (参数列表&#xff0c;可以没有参数){ //这里不能有r…

Unity PS5开发 天坑篇 之 URP管线与HDRP管线部署流程以及出包介绍04

目录 一, URP管线、HDRP管线下的Unity项目部署 1. PS5开发论坛关于Unity可支持的版本说明: 2. URP管线下的项目与部署 2.1 Build PS5 URP Project 2.2 运行画面 3. HDRP管线下的项目与部署 3.1 附上可以运行的画面: 4. PS5打包方式介绍 4.1 PC串流调试模式: Build Typ…

面试题--3.18

1. http与https的区别&#xff0c;以及https的认证过程及加密算法 &#xff1f; 区别&#xff1a; https协议需要到CA申请证书&#xff0c;一般免费证书较少&#xff0c;因而需要一定费用。 http是超文本传输协议&#xff0c;信息是明文传输&#xff0c;https则是具有安全性…

打造核心竞争力:高效Web系统数据中台的设计与实践_光点科技

在数字化的浪潮中&#xff0c;数据已经成为企业赖以生存和发展的核心资源。一个高效的Web系统数据中台&#xff0c;能够赋予企业在激烈的市场竞争中立于不败之地的能力。本文将深入探讨如何设计和实施一个能够提升企业数据管理水平和支持业务决策的高效数据中台架构。 数据中台…

接单收入超过主业!程序员必备的兼职接单平台汇总

这个月副业接单的款项到账了&#xff0c;由于接了个大单&#xff0c;这个月净收入5w&#xff0c;一下子副业收入超过了主业。不得不说&#xff0c;程序员接单确实是一条赚钱的新路子&#xff0c;都说节流不如开源&#xff0c;这下我是真的体会到了&#xff01; 今天&#xff0c…