今天(2011年5月31日),Twitter 发布了能够帮助我们的用户找到最相关的推文、图片和视频的
个人化搜索体验。要创造这个产品,我们的基础架构需要支持两项主要的功能:搜索结果的相关性过滤和对于相关图片以及视频的识别。两项功能都需要我们完全重写我们搜索架构,他们的核心就是
Blender 和
Earlybird。
搜索上的投入
自从我们在2008年收购了
Summize
以来,Twitter 不断在搜索上面加重了投入。我们的搜索团队从 3 人增长到了 15
人,并且把实时搜索引擎的可扩展性提高了两个数量级——这一切对于搜索基础架构的替换,都是在对既有服务没有主要任何干扰的情况下做到的。
搜索的进化背后的工程开发故事非常激动人心。原来的 Summize 基础结构使用 Ruby on Rails 作为前端以及 MySQL
作为后端(Twitter 和很多别的 startup 公司都用了同样的架构)。同时,Lucene
以及其他开源搜索技术并不支持实时搜索。结果就是,我们在 MySQL 里面构造了我们的倒排索引,利用其并发事务和 B-tree
数据结构来支持我们的并发索引和搜索。通过把索引分拆到多个数据库中以及使用多个前端副本(replication),这个基于 MySQL
的解决方案走得意想不到地远。2008 年的时候,Twitter 搜索的流量大概是 20 TPS(每秒推文)以及 200
QPS(每秒搜索)。在 2010 年 10 月的时候,当我们把 MySQL 换成了 Earlybird,这个系统平均能处理 1,000
TPS 以及 12,000 QPS。
Earlybird 是一个基于
Lucene 的实时倒排索引,在实时搜索上,它不仅给了我们比MySQL高一个数量级的性能,而且倍增了我们的内存使用效率,并提供了增加相关性过滤的灵活性。然而,我们还是需要换掉
Ruby on Rails 前端,因为它只能对 Earlybird 进行同步调用,并且在多年的扩张和向 Earlybird
的转移中,欠了一大堆技术债。
2011 年 4 月,我们发布了一个替代品,叫做 Blender。他把我们的搜索时延减到了原来的三分之一,带宽提高了 10
倍,并让我们终于能够从搜索基础架构里面去掉 Ruby on Rails。今天(2011 年 5 月)我们每秒大概索引 2,200
条推文,并提供 18,000 QPS 的搜索服务 (每天 16 亿次搜索!)。更重要的是,Blender
填补我们基础架构里面的一个空白,我们正需要这个东西来作一项自收购 Summize 以来最重要的面向用户的改动。
从 Hack-week 到产品化
当我们的团队发布 Earlybird
的时候,我们为其潜力而兴奋——它速度惊人,并且代码非常干净,且便于扩展。我们的一位工程师,Michael
Busch,在德国休假的时候,实现了一个图片和视频搜索的演示。一周后,在 Twitter 的第一次
Hack-week,搜索团队以及其它团队的一些成员完成了我们新的搜索体验的第一个演示。来自公司内部的反响非常积极,这个演示就成为了我们的产品规划的一部分。
寻找相关推文
Twitter 上有很多信息——平均而言,每秒钟有 2,200
条新推文产生!在重大事件发生的时候,比如日本海啸时,这个数字还会增加二到三倍。一般来说,我们只对最值得回忆的或是那些用户互动最多的推文感兴趣。在我们的新的搜索体验中,我们只把与某个特定的用户最相关的结果显示出来。所以,搜索结果被个人化了,我们也滤掉那些和其他用户不产生共鸣的推文。
为了支持这项相关性过滤和个人化功能,我们需要三类信号:
- 静态信号,在索引时加入
- 共鸣信号,随时间变化动态更新
- 关于搜索者的信息,搜索时提供
把所有的这些信号都放到我们的索引里面去意味着我们要改动我们的摄取(ingestion)流水线、Earlybird(我们的倒排索引)和
Blender(我们的前端)。我们还创建了一个新的更新器(updater)组件,其不断地把共鸣信号推送到
Earlybird。在摄取流水线中,我们加入了可以把静态信息标记到推文上一个新的流水线阶段。静态信息包括用户相关信息、推文文字的语言等等。然后,推文被复制到
Earlybird 的索引中(实时地)。我们扩展了 Lucene
的内部数据结构用以支持对于任意注解(annotation)的动态更新。动态更新(比如用户和推文的互动)会从更新器源源不断的送来。所有这些放在一起,Earlybird
和更新器就能够支持高速且非均匀地索引更新,且不需要使用并发锁或者拖慢搜索。
在搜索的时候,一台 Blender 服务器会解析用户的搜索词并将其和该用户的社交网络图一起发至多台 Earlybird
服务器。这些服务器会使用专门的排名函数合并多个相关性信号和社交网络图,为每一条推文计算出一个个人化的分数。分数最高且最新的推文会被送回到
Blender,然后它把它们合并起来、重新排名,然后最终返回给用户。
|
带有相关性支持的 Twitter
搜索基础架构 |
去除重复结果
重复和接近重复(类似)的推文在 Twitter
的搜索结果里面一般都不怎么有用。在流行或者重大事件发生的时候,也正是搜索应该对我们的用户最有帮助的时候,基本相同的推文就变得很多。即使这些重复的推文质量本身都很高,搜索者还是能获益于一个更多样化的结果集。我们使用一种基于
MinHashing
的技术来去除重复结果。对于每一条推文,我们计算多个签名(signature),两条推文如果有相同的签名集合则会被认为是相同。麻烦在哪儿?和
Twitter 所有的东西一样,短小才是关键:我们只有很少的内存预算能够用于存储签名。我们的算法把每一条推文都压缩到了 4
个字节,但是仍然能够使用极少的计算需求找到绝大多数的重复推文。
个人化
只有你通过选择关注感兴趣的账户的方式来个人化你的账户的时候,Twitter
才是最强大的。为什么你的搜索结果不该是个人化的呢?他们现在就是了!我们的排名函数会查看社交网络图和使用搜索者和推文作者的关系来计算分数。虽然社交网络图非常打,我们把每个用户最有用的信息都压缩到了一个
布隆过滤器(Bloom
Filter)里面,它提供给我们了空间上非常高效、常数时间的集合成员判断操作。在 Earlybird
扫描候选搜索结果的时候,它会看推文的作者在搜索者的社交网络图里面存在与否,并用此作为排名函数中的一个信号。
那些没有关注任何账号的用户也能从个人化机制中获益;比如,我们现在自动检测搜索者的偏好语言和地理位置。
图像和视频搜索
搜索推文和搜索推文中的实体(比如图片和视频)有着本质的不同。在前者中,判断一条推文是否和一个搜索匹配只需看推文的文字本身,无需别的信息。此外,每条推文的相关性信息也能用于排名和比较匹配的推文以找到最好结果。在搜索图片和视频的时候,情况就不一样了。比如,同样的图片可能被推了很多次,每次还都用了不同的关键字来描述图片。比如下面两条推文:
对于该图片的一种描述方法是,通过在推文中使用一系列的关键词,这里是“dog",“Australia”,以及“sheperd”,它们都描述了图片。如果一幅图片反复地在推文里面被一个关键词描述,那么这个图片多半和这个关键词有关。
哪到底什么让图片搜索成为了一个麻烦的问题呢?Twitter
允许你搜索几秒钟前的推文,里面的图片和视频也应该能够实时可用!Earlybird 使用
倒排索引来搜索。这额数据结构虽然非常高效,它们却不支持嵌入式(inline)更新,于是,这使得添加额外的关键字到已经索引好了的文档末尾几乎不可能。
如果即时性不重要的话,我们可以用
MapReduce
程序周期性地累积关键词集合并生成倒排索引。在这些离线索引中,每条指向图片或者视频的链接都会是一个文档,文档的文本就是聚集的关键字。然而,要满足我们的时延(latency)目标,我们需要没几秒钟就运行一次这些
MapReduce 程序,这个解决方案是不现实的。
取而代之,我们扩展了 Earlybird
的数据结构以支持高效的推文内实体(entities)查询。在搜索的时候,我们会寻找匹配推文的图片和视频,并把它们存在一个自定制的哈希表(hash
map)里面。每次有同样的 URL
加到哈希表里面的时候,它对应的计数器会增加。这个累计结束之后,我们将哈希表排序,并返回最佳的图片和视频用于显示。
下一步是什么?
我们搜索团队为能够创造驱动发现和帮助用户的创新性的搜索产品而感到兴奋。虽然新的搜索体验相比以前纯粹的实时搜索已经是重大的改进,我们才刚刚起步。在未来的几个月里,我们会改进搜索质量,扩张我们的基础架构,扩大我们的索引,并把相关性搜索带到移动设备上去。
如果你是一个富有才干的工程师,并且想为世界上最大的实时搜索引擎而工作,Twitter
搜索质量和
搜索基础架构团队正在招聘!
鸣谢
下列人员对本次产品发布有所贡献:Abhi Khune, Abdur Chowdhury, Aneesh Sharma,
Ashok Banerjee, Ben Cherry, Brian Larson, Coleen Baik, David Chen,
Frost Li, Gilad Mishne, Isaac Hepworth, Jon Boulle, Josh Brewer,
Krishna Gade, Michael Busch, Mike Hayes, Nate Agrin, Patrick Lok,
Raghavendra Prabu, Sarah Brown, Sam Luckenbill, Stephen Fedele,
Tian Wang, Yi Zhuang, Zhenghua Li。