50,000 封邮件,毫秒级搜索:MailVault 的本地索引如何工作
在 MailVault 里输入一个词,最新的匹配结果会在你按完键之前就出现在屏幕上。为了验证邮箱真正很大时依然如此,我们构建了一个 50,000 封邮件的保险库并为搜索计时。我们运行过的最慢的查询用了 14 毫秒。
这篇文章给出实际的数字、哪些测了哪些没测,以及数字背后的技术:一个住在你保险库里的小巧而朴素的 SQL 数据库。
测试方法
我们生成了 50,000 封邮件,每封约 3.3 KB,采用真实邮件常见的 HTML 加纯文本的多部分格式。它们分布在三个文件夹(收件箱、归档和已发送,各占大约三分之一)中,并以已知的比例植入已知的词:“invoice”出现在 5% 的邮件中,“budget”占 8%,“meeting”占 6%,另外还有日语和带重音的词,用来检验非英语搜索。每封邮件都经过 MailVault 真实的 MIME 解析器和真实的索引代码,使用的是发布版构建。
测试机器是一台配备 16 GB 内存的 Apple M4 Mac mini。它当时并不空闲:测试期间上面还在运行其他构建,所以请把这些耗时看作在一台忙碌的电脑上得到的结果。每个查询运行六次,三次在修复版的临时构建上,三次在合并后的代码上。下面的数字是这些运行的范围,每个时间都包含搜索本身以及构建你在列表中看到的结果行。
测试结果
50,000 messages · Apple M4 · warm cache · 6 runs each
query matches rows shown time
invoice ~2,500 500 7.1 – 7.5 ms
budget meeting 246 246 13.4 – 14.1 ms
update 4999 15 15 13.5 – 14.0 ms
会議 (Japanese) 1,529 500 4.3 – 4.6 ms
last 7 days, no words 1,008 500 1.0 ms
first index build 11 s (10.9 – 11.9 s)
index size on disk 225 MB
status check 5 ms
这张表有两点值得看。第一,耗时与匹配数量无关:15 条命中比 2,500 条用得更久,因为花时间的是查询的形态(两个词的短语比单个词更费事),而不是返回多少邮件。第二,列表最多显示最新的 500 条匹配,超出时会明确说明,所以无论匹配多少封邮件,绘制结果的工作量都是有上限的。

一次性的成本是构建索引:50,000 封邮件大约十一秒,约每秒 4,000 封,占用 225 MB 磁盘。它在后台进行,运行期间你可以继续使用 MailVault,之后只会为新增和变更的邮件建立索引。

1 ms 和 7 ms 是什么感觉
毫秒很难想象,所以这里放一把尺子。60 Hz 的显示器每 16.7 ms 重绘一次,而这里最慢的搜索也在一次重绘之内完成。可用性研究自 1993 年以来一直使用同样的三个界限:低于 0.1 秒感觉是瞬时的,最长 1 秒能让你的思路不被打断,到 10 秒时你的注意力就已经离开了。十四毫秒大约是“瞬时”界限的七分之一。日期筛选只要 1 ms,比这个界限低了一百倍。
服务器邮件、归档和备份:一次搜索

MailVault 把三类邮件并排放在一起:从服务器同步来的邮件、你归档的邮件,以及从备份还原的邮件。它们最终都是同一个保险库里的文件,所以都进入同一个索引。在测试中,邮件分布在收件箱、归档和已发送文件夹里,一次查询就覆盖了全部。邮件从哪里来,并不影响它被找到的速度。
只存在于服务器、从未下载过的邮件不在保险库里,所以本地索引并不知道它们。为此,MailVault 也会询问服务器,在服务器应答期间,先显示已编入索引的本地结果。这一段取决于你的服务商,因此我们不公布它的数字。Premium 可以一次搜索最多五个服务器邮箱,而不是一个。
外部备份驱动器是第二份冷副本。搜索在你的工作保险库上运行,当备份驱动器上也存在某封邮件的副本时,结果里会加上标记。
技术:一个非常快、非常简单的 SQL 数据库
没有搜索服务器,也没有云服务。索引就是保险库里的一个 SQLite 文件,而 SQLite 大概是全世界部署最广泛的数据库。MailVault 使用它的全文搜索引擎 FTS5,并配上 trigram 分词器:每个词都会被存成相互重叠的三字母片段。所以你输入“voic”就能找到“invoice”,无需通配符,也没有整词匹配的限制。重音符号会被折叠,因此搜索“reunion”也能找到“Réunion”。日语和中文的词与词之间没有空格,它们会走另一张专门为此建立的表。
速度来自三个朴素的决定:
- 搜索从不打开邮件文件。列表所需的一切(发件人、主题、日期、文件夹、标记)都存放在索引行里。在 50,000 封邮件的测试中,构建结果期间邮件解析器被调用了零次。
- 一个文件,一个进程。索引由 MailVault 的后台助手持有,只打开一次并一直保持就绪。没有需要启动的东西,也没有需要通过网络发送的内容。
- 你的磁盘始终是事实来源。索引是派生数据。如果它有一天损坏,MailVault 会根据你已保存的邮件重新构建它,绝不会碰邮件本身。
它可以离线工作,任何内容都不会离开你的电脑。
附件和图片(Premium)
免费搜索涵盖发件人、主题和邮件正文。Premium 还会加入附件内的文本,使用同一个索引、同一个搜索框:
- 附件文本。PDF、Word、Excel 和 PowerPoint 文件会被读取并把其中的文本编入索引,所以搜索某个合同编号,就能找到 PDF 里包含它的那封邮件。
- macOS 上的图片和扫描件文本。MailVault 使用你 Mac 上 Apple 的 Vision 框架来识别照片、截图和扫描页面中的文字,然后将其编入索引。识别在设备本地完成。Windows 和 Linux 没有这一步。
如果某条搜索命中只存在于附件之中,会用一个回形针标记出来,让你知道为什么一封正文平平无奇的邮件会出现在结果里。这次测试没有为附件提取计时:它取决于你的文件,并且是在后台对每个附件只运行一次,而不是在搜索时运行。
其他客户端如何描述它们的搜索
我们没有对其他邮件应用做过基准测试,下面这些应用也都没有公布 50,000 封邮件规模下的搜索耗时,所以这是设计上的比较,而不是秒表上的比较:
- Apple Mail:Apple 表示,Spotlight 首次建立索引可能需要数小时甚至数天,取决于数据量,并且“邮件”可能会显示索引尚未完成的提示。
- Outlook for Windows 使用 Windows Search 索引,Microsoft 指出,索引完成前结果可能不完整,只有本地缓存的邮件才会被编入索引,而且当搜索返回的结果太多时,较旧的项目可能被隐藏。
- Thunderbird 把全局索引保存在自己的数据库里。它的缺陷跟踪器中有长期存在的关于大型邮箱上索引变慢的报告,其中一位用户称 36,000 封邮件用了好几天。这些是用户在较旧硬件上的报告,不能与我们的测试直接比较。
我们的设计选择与通常的做法相反:我们为你自己保存的那份副本建立索引,把它放在一个小文件里,并让查询路径足够短,使得答案以毫秒计。
这次测试没有显示什么
这个邮箱是合成的。真实邮件更大也更杂乱,所以如果你的邮件正文很长,你的索引会更大,不过查询遍历的仍然是索引,而不是你的文件。这些耗时是在缓存已预热的情况下测得的:重启后的第一次搜索会从磁盘读取更多内容。测试涵盖主题、发件人和正文,不包含附件文本,并且只在一台机器上运行。
你可以自己重复这次测量:基准测试是 MailVault 源码树中被忽略(ignored)的 Rust 测试 search_index_bench_50k_real_parser,它会打印上面的每一个数字。
在毫秒之间找到六年前的那封邮件。
MailVault 在你的归档旁边保存一个私有的搜索索引,所以搜索 50,000 封邮件和搜索 500 封一样轻松。免费使用。附件和图片搜索包含在 Premium 中。
来源:Apple 支持,Spotlight 索引;Microsoft 支持,Outlook 搜索问题;Mozilla Bugzilla 585429。基准测试测量于 2026 年 9 月 21 日。