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

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.mzph.cn/news/772352.shtml

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

相关文章

clickhouse学习笔记02(小滴课堂)

ClickHouse核心基础-常见数据类型讲解 插入数据: decimal类型的数据,整数部分超了会报错,小数部分超了会截取。 查看表结构: 查询: 插入: 更新操作: 这个和mysql的语句不太一样。 删除语句和my…

Kafka总结问题

Kafka Kafka Kafka Kafka的核心概念/ 结构 topoic Topic 被称为主题,在 kafka 中,使用一个类别属性来划分消息的所属类,划分消息的这个类称为 topic。topic 相当于消息的分配标签,是一个逻辑概念。主题好比是数据库的表&#xff0…

【SpringBoot】实现一个简单的图片上传

前端上传表单 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <form enctype"multipart/form-data" method"post" action&q…

MCGS学习——弹框报警

弹框报警的制作流程 先绘制一个弹框窗口&#xff0c;在弹框窗口里绘制弹框标志&#xff0c;记得绘制完成之后点击合成单元&#xff0c;此外&#xff0c;打开报警信息按钮中是打开报警界面 绘制好之后&#xff0c;如果我们想让弹窗出现在我们想让他出现的位置&#xff0c;那我…

Likeshop回收租赁系统:回收租赁超方便!

尊敬的各位&#xff0c;很高兴有机会向大家介绍一款备受瞩目的系统——全新的Likeshop回收租赁系统。 无论是电子产品、衣服还是书本&#xff0c;越来越多人选择在二手平台上进行交易或租用&#xff0c;商品回收、租赁的需求越来越大&#xff0c;很多想入场的兄弟们都苦于没有…

(分享)一个图片添加水印的小demo的页面,可自定义样式

有时候想给某张图片添加一个自己的水印&#xff0c;但是又懒的下载相应软件&#xff0c;用js canvas制作一个静态页面&#xff0c;对于单张图片添加自定义文字水印&#xff0c;大小 间距&#xff0c;角度可调。 页面如下&#xff1a; 选择图片&#xff0c;设置相应参数&#x…

公众号超牛鼻的爆文仿写机器人,原创三篇只需6分钟,篇篇是爆文基因

大家好&#xff0c;我是大胡子&#xff0c;专注于RPA提效​&#xff0c;今天就介绍一款公众号超牛鼻的爆文仿写机器人​。 和以前的公众号爆文机器人不太一样&#xff0c;以前的爆文机器人需要手动插入图片、添加封面、插入话题&#xff0c;然后今天这个机器人就完全解决这几个…

代码随想录算法训练营第五十五天|583. 两个字符串的删除操作、72. 编辑距离

583. 两个字符串的删除操作 刷题https://leetcode.cn/problems/delete-operation-for-two-strings/description/文章讲解https://programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html视频讲解https://…

详解:写作和赚钱的 4 个关系!看完你一定会忍不住想开始写!

飞书文档的加密很强&#xff0c;也没有和自家的豆包大模型融合&#xff0c;所以只能通过其他方式获取文档的内容。 &#xff08;1&#xff09;将飞书文档转换为PDF&#xff0c;要用到浏览器插件&#xff1a; GoFullPage - Full Page Screen Capture - Microsoft Edge Addons …

RT-Thread动态内存扩展,使用多块不连续的RAM作为动态内存

开发环境 MCU&#xff1a;STM32F429VET6&#xff08;1M Flash&#xff0c;192K64K共256K SRAM&#xff09; 编译环境&#xff1a;MDK5.38 实时系统&#xff1a;RT-Thread标准版 目的 这颗MCU的SRAM默认是使用192K&#xff0c;即从地址0x20000000开始&#xff0c;最大0x30000…

第九届蓝桥杯大赛个人赛省赛(软件类)真题C 语言 A 组-分数

solution1 直观上的分数处理 #include <iostream> using namespace std; int main() {printf("1048575/524288");return 0; }#include<stdio.h> #include<math.h> typedef long long ll; struct fraction{ll up, down; }; ll gcd(ll a, ll b){if…

2024中国闪存市场观察:AI助推闪存全面起势?

过去两年&#xff0c;闪存市场一直处于低迷状态&#xff0c;但去年第四季度闪存颗粒资源的上涨&#xff0c;导致闪存产品价格一路上扬&#xff0c;市场遂发生反转。 2024年&#xff0c;中国闪存市场会彻底走向复苏&#xff0c;还是急转直下&#xff1f;中国AI热潮&#xff0c;…

MATLAB 自定义生成平面点云(可指定方向,添加噪声)(48)

MATLAB 自定义生成平面点云(可指定方向,添加噪声)(48) 一、算法介绍二、算法步骤三、算法实现1.代码2.效果一、算法介绍 通过这里的平面生成方法,可以生成模拟平面的点云数据,并可以人为设置平面方向,平面大小,并添加噪声来探索不同类型的平面数据。这种方法可以用于…

数据结构进阶篇 之 【二叉树】详细概念讲解(带你认识何为二叉树及其性质)

有朋自远方来&#xff0c;必先苦其心志&#xff0c;劳其筋骨&#xff0c;饿其体肤&#xff0c;空乏其身&#xff0c;鞭数十&#xff0c;驱之别院 一、二叉树 1、二叉树的概念 1.1 二叉树中组分构成名词概念 1.2 二叉树的结构概念 1.3 特殊的二叉树 2、二叉树的存储结构 …

全面:vue.config.js 的完整配置

vue.config.js是Vue项目的配置文件&#xff0c;用于配置项目的构建、打包和开发环境等。 在Vue CLI 3.0之后&#xff0c;项目的配置文件从原来的build和config目录下的多个配置文件&#xff0c;合并成了一个vue.config.js文件。这个文件可以放在项目的根目录下&#xff0c;用于…

AI入侵游戏业:是颠覆者还是创新助手?揭秘未来游戏新趋势!

在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已经成为各行各业的关注焦点。而在娱乐产业中&#xff0c;AI技术的引入也让人们对电子游戏的未来发展产生了无限遐想。那么&#xff0c;AI究竟会给电子游戏行业带来怎样的变革&#xff1f;它会成为行业的颠…

通讯录管理系统实现(C++版本)

1.菜单栏的设置 &#xff08;1&#xff09;我么自定义了一个showmenu函数&#xff0c;用来打印输出我们的菜单栏&#xff1b; &#xff08;2&#xff09;菜单栏里面设置一些我们的通讯录里面需要用到的功能&#xff0c;例如增加联系人&#xff0c;删除联系人等等 2.退出功能…

javaWeb私人牙科诊所管理系统

一、摘要 随着科技的飞速发展&#xff0c;计算机已经广泛的应用于各个领域之中。在医学领域中&#xff0c;计算机主要应用于两个方面&#xff1a;一是医疗设备智能化&#xff0c;以硬件为主。另一种是病例信息管理系统&#xff08;HIS&#xff09;以软件建设为主&#xff0c;以…

1978-2022年全国31省社会消费品零售总额数据

1978-2022年全国31省社会消费品零售总额数据 1、时间&#xff1a;1978-2022年 2、指标&#xff1a;社会消费品零售总额 3、范围&#xff1a;31省市 4、来源&#xff1a;整理自国家统计J和各省年鉴 5、缺失情况说明&#xff1a;1997-2022年31省市均无缺失&#xff0c; 199…

随机链表的深拷贝

目录 一、何为深拷贝&#xff1f; 二、题目 三、思路 1.拷贝节点插入到原节点后面 2.控制拷贝节点的random 3.脱离原链表 : 尾插的思想 四、代码 五、附加 一、何为深拷贝&#xff1f; 一个引用对象一般来说由两个部分组成&#xff1a;一个具名的Handle&#xff0c;也就…