如果我将用户分成碎片,我该如何提供“用户搜索”?显然,我不希望每次搜索都能击中每一个碎片.
长版
通过分片,我的意思是有多个数据库,每个数据库包含总数据的一小部分.对于(一个天真的)示例,数据库UserA,UserB等可能包含名称以“A”,“B”等开头的用户.当新用户注册时,我会简单地检查他的名字并将他置于正确的位置.数据库.当一个返回的用户登录时,我再次查看他的名字,以确定从中提取信息的正确数据库.
分片与读取复制的优点是读取复制不会扩展您的写入.所有写入主站的写入都必须转到每个从站.从某种意义上说,即使读取负载是分布式的,它们也都具有相同的写入负载.
同时,分片不关心彼此的写作.如果Brian在UserB分片上注册,则UserA分片不需要听到它.如果Brian向Alex发送消息,我可以在UserA和UserB分片上记录该事实.通过这种方式,当Alex或Brian登录时,他可以从他自己的分片中检索所有发送和接收的消息,而无需查询所有分片.
到现在为止还挺好.搜索怎么样?在这个例子中,如果Brian搜索“Alex”,我可以检查UserA.但如果他用他的姓氏“史密斯”搜索亚历克斯呢?每个碎片都有史密斯.从这里,我看到两个选项:
>让应用程序在每个分片上搜索Smiths.这可以缓慢完成(连续查询每个分片)或快速(并行查询每个分片),但无论哪种方式,每个分片都需要参与每次搜索.与读取复制不会缩放写入的方式相同,搜索每个分片都不会缩放搜索范围.您可能会达到搜索量足以压倒每个分片的时间,并且添加分片对您没有帮助,因为它们都获得相同的音量.
>某种索引本身可以容忍分片.例如,假设我有一个恒定数量的字段,我想搜索它:名字和姓氏.除了UserA,UserB等我还有IndexA,IndexB等.当一个新用户注册时,我将他附加到我希望他找到的每个索引.所以我把Alex Smith放入了IndexA和IndexS,他可以在“Alex”或“Smith”上找到,但没有子串.这样,您不需要查询每个分片,因此搜索可能是可伸缩的.
解决方法
显然,由于您将产生极高的延迟,因此不可能连续搜索每个碎片是不可能的.
因此,如果必须,您希望并行搜索.
有两个现实的选项,您已经列出了它们 – 索引和并行搜索.请允许我详细介绍一下如何设计它们.
您可以使用的关键洞察力是,在搜索中,您很少需要完整的结果集.您只需要第一页(或第n页)结果.因此,您可以使用相当多的摆动空间来缩短响应时间.
索引
如果您知道将搜索用户的属性,则可以为它们创建自定义的单独索引.您可以构建自己的inverted index,它将指向每个搜索词的(shard,recordId)元组,或者您可以将其存储在数据库中.懒惰地,异步地更新它.我不知道您的应用程序要求,甚至可能每晚都重建索引(这意味着您在任何一天都不会有最新的条目 – 但这对您来说可能没问题).确保优化此索引的大小,以便它可以适合内存;请注意,如果需要,可以对此索引进行分片.
当然,如果人们可以搜索“lastname =’Smith’或lastname =’Jones’”之类的东西,你可以阅读Smith的索引,阅读Jones的索引,并计算联合 – 你不需要存储所有可能的查询,只是他们的建筑部分.
并行搜索
对于每个查询,请将请求发送到每个分片,除非您知道要查找哪个分片,因为搜索恰好位于分发键上.使请求异步.获得第一页结果后立即回复用户;收集其余部分并在本地缓存,这样如果用户点击“下一步”,您将获得结果,不需要重新查询服务器.这样,如果某些服务器花费的时间比其他服务器长,则无需等待它们为请求提供服务.
在您使用它时,记录分片服务器的响应时间,以观察数据不均匀和/或负载分布的潜在问题.