Stalwart v1.0 发布:号称全球最快的邮件服务器
Stalwart v1.0: The Fastest Mail Server on the Planet
Stalwart 发布首个稳定版本 v1.0,重写解析器并引入 SIMD 与 SWAR,mail-parser 1.0 完整解析速度比 mailparse 快 3 倍、比 Dovecot 解析器快 9 倍。
2026 年 10 月 26 日 - 阅读时长 16 分钟
五年前,也就是 2021 年 10 月 3 日,第一个提交出现在一个名为 mail-parser 的小型仓库中。它只是一个用 Rust 编写的电子邮件解析库,仅此而已。在接下来的五年里,它发展成为一个完整的邮件与协作服务器,支持 JMAP、IMAP、POP3、SMTP、CalDAV、CardDAV 和 WebDAV,还包含垃圾邮件过滤器、集群功能,以及对六种存储后端的支持。今天,经过五年的持续开发,我们自豪地宣布 Stalwart v1.0,这是首个稳定版本。
1.0 版本从来就不打算成为又一个功能版本。它有两个目标。第一个是性能:让服务器的每个部分都尽可能快,并用数据来证明。第二个是数据本身的稳定性:最终确定数据库架构,使得从今往后,升级 Stalwart 只需安装新版本即可,不再需要像早期某些版本那样进行破坏性的迁移。在此过程中,一些新功能也顺带加入,包括对 PDF 和办公文档的全文搜索。
让我们逐一来看。
花费数月追逐每一微秒
本次发布的大部分工作都是在性能分析器下完成的。数月来,我们测量了 Stalwart 在哪些地方消耗 CPU 周期、内存和数据库往返,重写了所有突出的部分,然后再次测量。本次发布包含近 200 个提交,涉及 1,300 多个文件,其中绝大多数都是为了让某些东西更快或更轻。
用 SIMD 和 SWAR 重写解析器
邮件服务器花费在查找特定字节上的时间占比高得惊人:头部行的结尾、MIME 边界、quoted-printable 正文中的等号、结束 IMAP 标签的空格。如果一次只处理一个字节,对于一封 10 MB 的邮件来说,那就是大量的循环迭代。有两种技术可以让 CPU 一次处理多个字节来完成这项工作:
- SIMD(单指令多数据)利用现代处理器的宽向量寄存器(x86-64 上的 SSE2、SSSE3 和 AVX2,ARM 上的 NEON),用一条指令比较或转换 16 或 32 个字节。
- SWAR(寄存器内 SIMD)用普通的 64 位整数实现同样的思路:通过几个精心选择的算术和位运算,一次测试一个字(word)的全部八个字节。它不需要特殊指令,因此能在所有 CPU 上运行。
Stalwart 现在在所有关键之处都同时使用这两种技术。在技术栈的底层,有两个库与服务器一同达到 1.0:
- mail-parser 1.0,正是 Stalwart 起步时所用的那个库,已从零重写。SIMD 内核定位 MIME 边界和头部字段,SWAR 处理头部名称,解析后的邮件存放在少量扁平向量中,正文只有在有人实际读取时才会被解码。特定于 CPU 的内核在运行时选择,每一个都针对普通的标量参考实现进行了模糊测试。与其他解析器相比,它比流行的 Rust
mailparsecrate 快 3 倍,比 Dovecot 自带的解析器快 9 倍,在完整解析时比 libEtPan!、VMime 和 GMime 快 15 到 31 倍。 - encodify 是一个新的编码库,为 Base64、Base32 和 quoted-printable 提供 SIMD 内核,这些编码承载了电子邮件中几乎所有的附件和非 ASCII 标头。在 AMD EPYC 服务器上,解码 MIME 封装的 Base64 附件比使用标准 Rust
base64crate 快 22 倍,quoted-printable 解码比使用quoted_printablecrate 快 9 倍。它现在处理服务器中所有的 Base64、quoted-printable 和 IMAP UTF-7 转换。
在其之上,协议层也得到了同样的处理。IMAP 命令解析器现在是表驱动的,扫描整段字节,而不是逐字符步进状态机。SMTP 命令解析器、JMAP 背后的 JSON 解析器、垃圾邮件过滤器分词器、HTML 转文本转换器、变长整数编解码器和日志序列化器都使用 SWAR 扫描。甚至管理员用于路由和过滤决策的表达式语言,现在也编译为紧凑的字节码,为常见比较提供专用指令,并在回收的内存区域中求值。
更少、更快的数据库往返
Section titled “Fewer, faster trips to the database”
如果每个请求随后都要等待数据库,快速解析就白费了,因此我们在 Stalwart 如何与其存储通信上投入了同样多的时间。标识符现在在使用它们的同一写事务内分配,从每次邮件投递中消除了整轮往返。读-改-写更新,例如添加关键字或追加到搜索索引,在后端内作为合并操作运行。存储的对象使用 zstd 压缩,字典专门针对电子邮件、日历和联系人数据训练。
每个后端也单独进行了调优:
- RocksDB:每种数据现在都存放在按访问方式配置的列族中,点查询使用布隆过滤器,扫描使用大块,压缩针对频繁变动的数据调优。热计数器,例如队列配额,分散到最多 256 个槽位以避免写冲突。
- PostgreSQL 和 MySQL:预生成并缓存预处理语句,分块迭代并自动重试,每次写入的往返次数更少。
- FoundationDB:事务将需要的读取在并行窗口中预取,大值分块组装。
- SQLite:页面大小、缓存、内存映射和 WAL 设置针对邮件工作负载调优。
存储层之上是重新设计的缓存子系统。每个账户的邮件索引以紧凑的打包形式保存在内存中,IMAP 排序、线程化、标志获取、邮件属性搜索和重新同步现在都从该缓存应答,很少或完全不读取数据库。超大账户获得自己的缓存层并配有内存预算,缓存快照可以换出到本地磁盘、Redis 或 blob 存储,而不是重建。
新的搜索引擎
Section titled “A new search engine”
内置全文搜索引擎是从头重写的。旧设计为每个词存储一个位图,并且没有词位置的概念。新引擎以 65,536 个文档为块存储倒排列表,并为每条消息保留词位置。这使得真正的短语搜索成为可能,并且让单个邮箱内的搜索只读取该邮箱涉及的块。它索引词干和前缀,使用 zstd 压缩数据,并通过带并发控制、重试和重建的后台队列索引新邮件。PostgreSQL 和 MySQL 搜索后端也进行了重构。在一个包含 250,000 条消息的邮箱上,对每条消息正文进行全文搜索现在可在 4 毫秒内返回。
在数百个 vCPU 上进行压力测试
在没有真实负载下进行测量的情况下优化代码只是猜测,因此我们为本次发布配套搭建了一个专门的基准测试和压力测试实验室。它运行在 Microsoft Azure 上,覆盖 30 多个区域,最多可并行运行 18 个完整测试实验室,同时有数百个 vCPU 处于忙碌状态。
测试目录涵盖 35 种测试,分布在 550 多个独立测试单元中,覆盖:
- 所有主要协议:IMAP、JMAP(包括通过 EventSource 和 WebSocket 的推送)、SMTP 入站、提交和中继、CalDAV、CardDAV 和 WebDAV。
- 以 PostgreSQL、FoundationDB 和 RocksDB 作为数据存储,Redis 作为内存存储,S3 和文件系统作为 blob 存储,以及混合负载下的三节点集群。
- 大型数据集:500 个账户中的 160 万条消息和 750,000 个日历与联系人项目,单个邮箱包含 100 万、200 万和 500 万条消息,单台服务器上 100,000 个账户,1000 万条消息的邮件队列,以及一百万份报告的 DMARC 和 TLS 报告洪流。
- 风暴和滥用:每秒 20,000 个新连接、大规模登录、每个账户数千个 IDLE 和推送订阅者、恶意和畸形消息、慢速 loris 客户端和超大字面量。
- 六小时浸泡测试,用于查找内存增长、缓存漂移和缓慢泄漏,其中包括一个通过 MTA 推送超过 1000 万条消息的测试。
整个过程中对运行进行了性能分析。使用 perf 对 CPU 时间进行采样并转换为火焰图,off-CPU 分析显示线程在何处等待锁和 I/O,使用 jemalloc 和 heaptrack 对堆进行分析,并且一个特殊的插桩构建按命令统计每次存储往返,以便我们准确看到哪些请求对数据库的访问次数超出应有水平。然后,每个修复都在匹配的机器上与之前的构建进行对比测量,之后才被接受。
本周期的第一轮完整测试发现了 64 个问题,这些问题已修复并重新测试。第二轮重新运行了所有内容,又修复了 33 个问题。以下是实验室发现的一些示例:
- 重新打开包含 500 万条消息的邮箱从 61 毫秒降至 0.21 毫秒。
- 对包含 500 万条消息的账户进行 JMAP 查询从 3.3 秒降至 12 毫秒。
- RocksDB 上的入站邮件吞吐量峰值几乎翻倍,从每秒 9,690 条增至 18,394 条。
- 发现并修复了一个内存问题,该问题中进程占用高达 9.6 GB,而实际堆内存低于 0.4 GB。在六小时浸泡测试中,内存现在保持平稳。
这个实验室并非为本次发布所做的一次性工作。它现在已成为 Stalwart 内部测试方式的一部分:未来每个版本在发布前都会经过相同的基准测试和压力测试轮次,因此性能提升得以保持。
比 Postfix 和 Dovecot 更快
内部数字只能说明 Stalwart 比它自己以前更快了。我们还想知道它与世界上大多数人使用的系统相比如何。于是我们将 Stalwart v1.0 与部署最广泛的开源邮件栈进行了正面比较:SMTP 使用 Postfix 3.11,IMAP 使用 Dovecot 2.4。
两者都运行在相同的 Azure 服务器上(8 个 AMD EPYC Zen 5 vCPU、32 GiB 内存、本地 NVMe 存储),加载了相同的 1,000 个邮箱以及三个最多包含 250,000 封邮件的超大邮箱,并接收完全相同的工作负载:接收邮件、投递邮件以及向 IMAP 客户端提供邮件服务。加密、密码哈希和全文搜索在两者上的配置方式相同,并且两个系统在同一硬件上交替进行测量。
在 38 项主要对比中,Stalwart 有 35 项更快。
部分结果如下:
- IMAP:在 15 项日常客户端操作中,Stalwart 有 14 项比 Dovecot 更快,快 1.4 到 19 倍。带完整 TLS 握手的登录耗时 5.1 ms 而非 28 ms,获取 1 MB 邮件快 6 倍,对 INBOX 进行线程化快 5 倍,移动或删除 100 封邮件快 18 到 19 倍。
- SMTP:Stalwart 接收邮件的速度比 Postfix 快 3.6 到 12 倍,并且每秒可维持 5,445 封入站邮件,而 Postfix 为 723 封。
- 投递:邮件对 IMAP 客户端可见的速度快 16 倍,处于 IDLE 等待状态的客户端在 3.3 ms 内收到通知,而非半秒。
- 负载:Stalwart 每秒可服务的并发 IMAP 会话数多 5.4 倍,在开始变慢之前可中继的邮件量多 3.8 倍。
差距随邮箱规模增大而扩大。在包含 250,000 封邮件的邮箱上,对每封邮件正文进行全文搜索在 Stalwart 中耗时 3.75 ms,在 Dovecot 中耗时 136 ms;移动或删除 25,000 封邮件约在十分之一秒内完成,而非 24 到 34 秒:快 200 倍以上。
Dovecot 胜出的三项测试都是同一操作:一次性将邮箱中的所有邮件标记为已读。
我们只测试了 Postfix 和 Dovecot,但它们并非普通的两款产品。它们几乎是所有流行的自托管邮件服务器背后的引擎:GitHub 上星标超过 10,000 的每个开源容器化邮件服务器,包括 docker-mailserver 和 mailcow,底层都运行 Postfix 和 Dovecot,Mail-in-a-Box 也是如此。在几乎所有基准测试中击败这一组合,意味着击败了互联网上大多数自托管邮件背后的技术栈。我们现在可以坦率地说:Stalwart 是地球上最快的邮件服务器。
不要只听我们的一面之词。每个脚本、配置、测试场景和原始结果都在 benchmarks 仓库中,同时还有方法论和一份坦率的注意事项清单,例如两个系统在磁盘同步行为上的差异。任何拥有 Azure 账户的人都可以配置相同的服务器并运行整套测试。针对 Stalwart 不同存储后端和集群设置的基准测试正在准备中,将放入同一仓库。
性能是重头戏,但 v1.0 还带来了一系列新功能:
- 文档内搜索:Stalwart 现在会提取并索引 PDF、Microsoft Office(
docx、xlsx、pptx)、OpenDocument(odt、ods、odp)、EPUB 和 RTF 文档中的文本,涵盖电子邮件附件和文件存储中的文档。现在搜索某个短语,就能找到去年那封邮件所附的合同,而不只是提到它的那封邮件。该提取器完全用 Rust 从零编写,不依赖任何外部文档库,并在严格的大小和复杂度限制下处理每个文档,因此恶意或格式错误的文件无法耗尽服务器资源。 - 文件存储全文搜索,以及带按分区状态、重试和重建的索引队列。
- 元数据:用于电子邮件、邮箱、日历、联系人、文件等的 JMAP Object Metadata,以及 IMAP METADATA(RFC 5464)。
- 后量子 DKIM2:为 DKIM2 提供 ML-DSA-44 与 Ed25519 复合签名。
- OAuth 与 OpenID Connect:OAuth Client ID Metadata Documents、按客户端划分的邮件、联系人、日历和文件作用域、用户可撤销的已连接应用列表,以及针对外部身份提供商的受众匹配、不透明令牌验证和无作用域令牌策略。
- 存储与运维:面向单节点部署的内置内存存储、Azure Blob Storage 的更多身份验证方法、用于故障转移的辅助 LDAP 服务器、缓冲数据的内存预算、对每个出站 HTTP 请求的 SSRF 防护,以及可配置的客户端 IP 来源和受信任代理跳数。
- AI 模型和 AI 垃圾邮件分类器(位于企业版),支持 OpenAI 兼容、Anthropic、Gemini 和 Ollama 模型。
完整列表见变更日志。
升级到 v1.0
最终确定数据库架构需要一次性付出代价:Stalwart v1.0 使用新的数据库布局,现有数据必须迁移到该布局。v1.0 服务器无法打开由早期版本创建的存储。
我们知道过去的一些升级并不顺利,我们对此非常重视。此次迁移经过精心设计和测试,力求尽可能平滑,而且这是最后一次此类迁移:架构现已稳定,未来的版本将原地升级。有两种方式可以升级到 v1.0。
迁移工具
对于运行 Stalwart v0.16 的部署,stalwart-migrate 是最简便的途径。它要求 v0.16.27,因此更早的 0.16 版本应先更新到该版本。该工具会将你的设置转换为 v1.0 架构,并将所有数据复制到新的空 v1.0 存储中。它只从旧存储读取,因此原始部署保持完好,在切换前的任何时刻都可以重新启动。
它还支持增量迁移。首次复制在 v0.16 继续为用户服务的同时运行,无论耗时多久。之后的每次运行只复制自上次以来发生变化的内容,唯一的停机时间是 v0.16 停止后的短暂最终运行。账户和消息标识符、IMAP UID 和变更历史都会保留,因此 IMAP 和 JMAP 客户端无需重新下载任何内容即可继续使用。使用 OAuth 登录的用户需要重新登录,全文索引会在切换后在后台重建。
迁移代理与 Vandelay
对于运行 Stalwart v0.15 或更早版本的用户,或者任何希望逐个迁移用户的人,迁移指南描述了第二种方式。迁移代理位于旧服务器和新服务器之前,将每个用户发送到持有其账户的那一台服务器,而Vandelay则逐个、增量地复制账户。每个账户通过翻转映射表中的一个条目来切换,该用户不会停机,并且在你决定停用旧服务器之前,旧服务器会保留完整副本。同样的流程也适用于从 Dovecot 和 Postfix 迁移到 Stalwart。迁移概览比较了这两种方式。
你的设置结构基本保持不变。大约一百个键被重命名以遵循一致的命名约定,少数对象(OIDC 目录、AI 模型、Azure 存储、转发头)被重构,而 stalwart-migrate 会自动转换所有这些内容。升级前有两项变更需要注意:
- 集群需要 Redis。集群协调器现在需要一个基于 Redis 的内存存储(Redis、Redis Cluster 或 Redis Sentinel)。早期版本可以回退到数据存储来实现这一点,但它在高负载下速度很慢,因此已被移除。单节点部署则获得一个内置的本地内存存储。
- OAuth 现在遵循开放公共客户端配置文件。Stalwart 现在完全强制执行OAuth Profile for Open Public Clients,这是一项新兴标准,允许任何邮件客户端使用 OAuth 登录任何邮件服务器,而无需事先注册。Stalwart 自带的授权服务器开箱即支持它。许多外部身份提供商尚未支持该配置文件要求的所有内容,例如动态客户端注册,并且一些客户端尚未请求所需的作用域。
如果你使用不符合规范的外部身份提供商,可以通过 OIDC 目录上的兼容性设置放宽 Stalwart 的令牌检查,风险自负,因为每一项都会削弱一项安全检查。这些设置主要让 WebUI 等 Web 客户端保持正常工作。它们无法解决的是提供商本身缺少该配置文件的问题,而这很快就会变得重要:包括 Apple Mail 在内的流行电子邮件客户端正在采用该配置文件,一旦它们采用,其用户将无法针对不符合规范的提供商进行身份验证。如果你依赖外部身份提供商,现在是要求他们支持该配置文件的好时机。
一项相关变更影响 WebUI。它不再使用固定的客户端 ID,而是通过Client ID Metadata Document(一个描述客户端的 URL)向身份提供商标识自己。一些身份提供商尚不支持这一点。对于这些提供商,管理员可以在提供商处为 WebUI 注册一个客户端,并在oauthClientId中设置其 ID,WebUI 就会像以前一样登录。详细信息见client id of the WebUI。
我们随时提供帮助
Section titled “We are here to help”
如果你在迁移过程中遇到问题或有疑问,请在support.stalw.art上发布。在整个迁移期间,我们将每天在那里回答问题。
五年前,这还只是一个用于解析电子邮件的 Rust 库。如今,它已经是一个完整的邮件与协作服务器,而在 v1.0 版本中,它的速度比我们测过的任何其他方案都要快。这一切都离不开社区:每一位提交 issue、发送补丁、在论坛上回答问题,或是信任 Stalwart 来托管自己邮件的人。谢谢你们。
下载 Stalwart v1.0,并告诉我们你的想法。
来源:Hacker News · stalw.art