Skip to content
← 返回博客

50,000 封邮件,毫秒级搜索:MailVault 的本地索引如何工作

7 分钟阅读

在 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 条匹配,超出时会明确说明,所以无论匹配多少封邮件,绘制结果的工作量都是有上限的。

MailVault 在一个包含 50,000 封邮件的保管库中搜索 invoice 一词的结果。搜索框下方的标题说明列表显示所有文件夹中约 2,500 条已保存匹配结果里最新的 500 条,下面是一列邮件行。
应用中的同类搜索:在一个包含 50,000 封邮件的保管库里搜索“invoice”。列表显示约 2,500 条已保存匹配结果中最新的 500 条,并明确说明了这一点。顶部的几行来自共用这个窗口的演示邮箱,保管库的文件夹名为 Projects、Correspondence 和 Clients,与基准测试中的不同。
Search time per query on 50,000 messages, in milliseconds Five bars: last 7 days with no words 1.0 ms, Japanese query 4.5 ms, invoice 7.4 ms, budget meeting 14.0 ms, update 4999 14.0 ms. Every bar ends before the 16.7 ms mark, which is one screen refresh at 60 Hz. 0 5 10 15 20 milliseconds, search plus building the result rows last 7 days, no words 1 ms 会議 (Japanese) 4.5 ms invoice 7.4 ms budget meeting 14 ms update 4999 14 ms one 60 Hz screen refresh: 16.7 ms
在 50,000 封邮件上每个查询的搜索耗时。每根柱子都在 60 Hz 下一次屏幕刷新之前结束。

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

MailVault 设置的“存储”标签页中的搜索索引卡片,显示已索引 50,000 / 50,000 和约 270 MB,并带有邮件正文、附件文本和图片文字的开关。
设置、存储、搜索索引构建完成后的状态:50,000 封邮件中已索引 50,000 封,磁盘占用约 270 MB。这是在应用中单独进行的一次运行,因此大小与上方基准测试表中的 225 MB 不同。

1 ms 和 7 ms 是什么感觉

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

How long is a millisecond? Search times on a logarithmic time scale A logarithmic ruler from 1 millisecond to 100 seconds. MailVault search sits between 1 and 14 milliseconds, close to one screen refresh at 16.7 milliseconds and far below the 100 millisecond point at which a response feels instant. The old file-by-file search projected to 88 seconds on a 20,000-message folder. 1 ms 10 ms 100 ms 1 s 10 s 100 s MailVault search on 50,000 messages: 1 to 14 ms one screen refresh 16.7 ms at 60 Hz feels instant under 100 ms flow of thought up to 1 s attention lost after 10 s old search 88 s (projected)
时间采用对数刻度,从 1 毫秒到 100 秒,每一格是前一格的十倍。响应时间限制来自:Jakob Nielsen。88 s 这一点,是最初的逐文件搜索在 20,000 封邮件的文件夹上的推算耗时。

服务器邮件、归档和备份:一次搜索

MailVault 某个查询的搜索结果,底部的图例说明了仅在服务器、在你的保险库里、唯一副本和在备份驱动器上这几种标记
每条结果都会说明邮件存放在哪里:仅在服务器上、在你的保险库里、唯一副本,或在你的备份驱动器上。

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 日。