Vespa vs.Elasticsearch可匹配数百万人

作为一个约会应用程序,体验的一个组成部分是根据你和你的潜在伴侣所设定的无数偏好向你推荐伟大的潜在伴侣。正如你所想象的,优化这部分体验有很多激励因素,因为这是每个人在获得匹配之前开始的第一步对话,等等。

然而,在我们向您推荐潜在匹配项(或向您推荐其他潜在匹配项)时,您设置的首选项并不是唯一的因素。如果我们只是显示所有符合您标准的用户,而不进行任何排序,最终结果将是更少的匹配项。例如,如果我们不尝试将用户最近的活动纳入结果中,您花费更多时间与未使用应用程序rece的人进行交互的可能性会高得多这当然不会让用户成功!除了你和其他人设定的偏好之外,我们还利用许多算法和因素来推荐我们认为你应该看到的用户。

在提供推荐时,我们需要在该时间点提供最佳结果,并允许您不断查看更多您喜欢的推荐或传递您的潜在匹配项。在内容本身可能不会经常更改或此类及时性不太重要的其他应用中,这可以通过离线系统完成,重新生成经常使用se推荐。例如,当使用Spotify的“每周发现”功能时,您可以欣赏一组推荐曲目,但这组曲目将被冻结到下周。对于OkCupid,我们允许用户无休止地实时查看他们的推荐。“内容”我们推荐的——我们的用户——本质上是高度动态的(例如,用户可以加入、更改其偏好、配置文件详细信息、位置、随时停用等)并且可以更改推荐对象和方式,因此我们希望确保您看到的潜在匹配是您当时看到的最佳推荐。188bet金宝搏官网

为了利用各种排名算法,同时能够持续提供实时推荐,我们需要使用一个搜索引擎,该引擎不断更新用户数据,并提供过滤和排名潜在候选人的能力。

现有匹配系统有哪些问题

188bet金宝搏官网OkCupid多年来一直在使用定制的内部匹配系统。我们不会详细介绍匹配系统,但在较高的层次上,想象一个用户空间碎片上的map reduce框架,每个碎片在内存中包含一些相关用户数据,用于动态处理各种过滤器和排序。搜索扇形展开到所有碎片,最终合并结果以返回前k个候选。这个定制的匹配系统为团队提供了很好的服务,那么为什么我们现在决定改变这个系统呢?

随着团队的发展,为了在未来几年支持各种基于建议的项目,我们知道我们需要改进这个系统。最大的难点之一是在开发模式更新时,比如添加关于用户的新数据(例如,用户首选的性别标签)需要数百到数千行样板代码,部署需要仔细协调,以确保系统的所有部分都按正确的顺序部署。简单地尝试添加一种新方法来过滤用户集或添加一种新方法来对结果进行排序需要工程师半天的时间来手动部署到prod中的每个碎片恢复并随时了解可能出现的问题;回滚速度并没有快多少。更重要的是,由于碎片和副本是手动分配的,并分布在一组裸机上,因此系统的操作和扩展变得越来越困难。2019年初,随着match系统负载的增加,我们需要增加search容量,因此我们通过在多台机器上手动放置服务实例来添加另一个副本集—这是后端和操作团队之间的一项为期数周的工作。此时,我们还开始注意到内部构建的服务发现系统、消息队列等中的性能瓶颈,而这些组件以前都提供过服务该公司嗯,我们正在达到一个负载点,在这个点上,我们不确定这些子系统中的任何一个本身是否可以扩展。我们的目标是将更多的工作负载转移到云环境中,而转移匹配的系统,这本身是一项艰巨的任务,还需要带上所有其他子系统组件。

如今,在OkCup188bet金宝搏官网id,这些子系统中的许多都由更健壮的OSS云友好选项提供服务,在过去两年中,该团队采用了各种不同的技术,取得了巨大成功。在这篇博文中,我们将不讨论这些努力,而是将重点放在我们为解决上述问题所做的努力上,通过转向一个对开发人员更友好、更具可扩展性的搜索引擎来获取我们的建议:韦斯帕.

这是一场比赛!为什么O188bet金宝搏官网kcupid与Vespa匹配

从历史上看,OkCupid188bet金宝搏官网是一个小团队,我们很早就知道解决搜索引擎的核心问题将是极其困难和复杂的,因此我们研究了可以支持我们用例的开源选项。两大竞争者是Elasticsearch和Vespa。

这是一个受欢迎的选项,有一个大型社区、文档和支持。它有许多特性,甚至被导火线. 就开发经验而言,可以使用PUT映射添加新的模式字段,可以通过结构化REST调用完成查询,对查询时间排序有一定的支持,能够编写自定义插件,等等。在扩展和维护方面,您只需确定碎片的数量,系统就会为您处理副本的分发。缩放需要重建另一个具有更高碎片数的索引。

我们选择退出Elasticsearch的最大原因之一是缺少真正的内存部分更新。这对于我们的用例非常重要,因为我们要索引的文档,我们的用户,需要通过喜欢/传递、消息传递等频繁地更新。与内容相比,这些文档本质上是高度动态的我不喜欢广告或图像,因为它们大多是静态对象,属性很少更改,因此更新时低效的读写周期是我们主要的性能问题。

这只是几年前开放的源泉声称支持在用户服务时存储、搜索、排名和组织大数据。Vespa支持

  • 通过真正的内存中部分更新实现高提要性能,无需重新索引整个文档(据说每个节点每秒高达40-50k更新)
  • 提供灵活的排名框架,允许在查询时进行处理
  • 直接支持在排名中与机器学习模型(如TensorFlow)集成
  • 查询可以通过REST调用中的表达性YQL(Yahoo查询语言)完成
  • 通过Java组件自定义逻辑的能力

当涉及到扩展和维护时,您需要不要想碎片再也没有了-您可以配置内容节点的布局,Vespa会自动处理将文档集拆分为存储桶、复制和分发数据的操作。此外,无论何时添加或删除节点,数据都会从副本中自动恢复和重新分发。扩展仅仅意味着更新配置以添加节点,并让Vespa自动重新分发这些数据。

总体而言,Vespa似乎最能支持我们的用例。OkCupid整合了许多关于用户的不同信息,以帮助他们找到最佳匹配-就过188bet金宝搏官网滤器和排序而言,每个都有100多个!我们将始终添加更多的过滤器和排序,因此能够支持该工作流非常重要。在写入和查询方面,Vespa与我们现有的匹配系统最为相似;也就是说,我们的匹配系统还需要处理内存中的快速部分更新,并在查询时实时处理排名。Vespa也有一个更加灵活和直接的排名框架;与Elasticsearch查询的笨拙结构相比,YQL表达查询的能力只是另一个不错的优势。在扩展和维护方面,Vespa的自动数据分发功能对我们相对较小的团队规模极具吸引力。总而言之,Vespa似乎可以更好地支持我们的用例和性能需求,同时与Elasticsearch相比更易于维护。

Elasticsearch更广为人知,我们可以从Tinder对它的使用中学习,但任何一种选择都需要大量的前期研究和调查。Vespa已经服务于许多生产用例,如泽奇,Flickr提供数十亿张图像,以及雅虎双子座广告该平台每秒有超过10万个请求,可向每月10亿活跃用户提供广告服务。这让我们相信,这是一个经过战斗考验、性能优良且可靠的选择——事实上,Vespa的起源已经被证明是正确的持续时间更长而不是弹性研究。

此外,Vespa团队一直非常涉及和乐于助人。Vespa最初是为服务广告和内容页面提供服务,并且据我们所知,它尚未用于约会平台。我们对Vespa的初步使用挣扎,因为它是如此独特的用例,但Vespa团队一直非常响应,并迅速优化该系统,帮助我们处理出现的几个问题。

Vespa如何工作以及OKCupid的搜索样子188bet金宝搏官网

在深入研究VESPA用例之前,这里是关于Vespa如何工作的快速概述。Vespa是众多服务的集合,但是每个DOCKER容器都可以配置为满足管理员/配置节点、无状态java容器节点和/或状态C++内容节点的角色。、ML模型等可通过州API配置集群,该集群处理对容器和内容集群应用更改。Feed请求和查询都通过HTTP通过无状态Java容器(允许定制处理),然后Feed更新进入内容集群,或者查询散开到执行分布式查询的内容层。在大多数情况下,部署一个新的应用程序包只需几秒钟,Vespa会在容器和内容集群中处理这些更改,因此您几乎不需要重新启动任何东西。

我们在Vespa集群中维护的文档包含关于给定用户的无数属性模式定义定义文档类型的字段以及排名档案包含适用的排名表达的集合。假设我们有一个模式定义这样表示用户:

搜索用户{

文档用户{

字段用户标识类型长{
索引:摘要|属性
属性:快速搜索
排名:筛选器
}

字段拉长类型位置{
索引:属性
}

#UNIX时间戳
字段类型为long{
索引:属性
属性:快速搜索
}

#包含此用户文档喜欢的用户
当发生时,相应的权重是UNIX时间戳
类似字段的serset类型weightedset{
索引:属性
属性:快速搜索
}

}

秩配置文件myRankProfile继承默认值{
秩属性{
查询(lastOnlineWeight):0
查询(IncomingLikeWight):0
}

函数lastonlinescore(){
表达式:查询(lastOnline权重)*新鲜度(lastOnline)
}

函数incomingLikeTimestamp(){
表达式:rawScore(likedUserSet)
}

函数hasLikedMe(){
表达式:if(incomingLikeTimestamp>0,1,0)
}

函数incominglikescore(){
表达式:查询(incominglikewight)*hasLikedMe
}

第一阶段{
表情{
lastOnlineScore+收入类Core
}
}

摘要 - 特征{
lastOnlineScore收入情况
}
}

}

这个索引:属性名称表示这些字段应保存在内存中,以使我们能够在这些字段上获得最佳的写入和读取性能。

假设我们用这样的用户文档填充集群。然后我们可以对上面的任何字段进行搜索筛选和排序。例如,我们可以向默认搜索处理程序发出POST请求http:// localhost:8080 /搜索/找到用户,除了我们自己的用户777.,距离我们的位置不到50英里,自时间戳以来一直在线1592486978,按最近的活动排序,并保留前两名候选人。让我们也选择概要为了帮助我们了解排名档案中每个排名表达式的贡献:

{
“yql”:“从用户选择用户ID,summaryfeatures,其中lastOnline>1592486978和!(用户ID包含\“777\”)限制2;”,
“排行”: {
“档案”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”
}
},
“pos”:{
“半径”:“50mi”,
“ll”:“N40o44'22;W74o0'2”,
“属性”:“latLong”
},
“介绍”:{
“摘要”:“默认”
}
}

我们可能会得到一个结果:

{
“根”:{
“id”:“顶级”,
“相关性”:1.0,
“字段”:{
“总数”:317
},
“覆盖范围”:{
“覆盖范围”:100,
“文件”:958,
“完整”:正确,
“节点”:1,
“结果”:1,
“结果完整”:1
},
“儿童”:[
{
“id”:“索引:用户/0/bde9bd654f1d5ae17fd9abc3”,
“相关性”:48.99315843621399,
“来源”:“用户”,
“字段”:{
“userid”:-5800469520557156329,
“suffilefeatures”:{
“rankingExpression(incomingLikeScore)”:0.0,
“rankingExpression(lastOnlineScore)”:48.99315843621399,
“vespa.summaryfeatures.cached”:0.0
}
}
},
{
“id”:“索引:用户/0/E8AA37DF0832905C3FA1DBD”,
“相关性”:48.99041280864198,
“来源”:“用户”,
“字段”:{
“userid”:6888497210242094612,
“suffilefeatures”:{
“rankingExpression(incomingLikeScore)”:0.0,
“rankingExpression(lastOnlineScore)”:48.99041280864198,
“vespa.summaryfeatures.cached”:0.0
}
}
}
]
}
}

在对匹配的点击进行过滤后第一阶段对排名表达式进行求值以对点击进行排名关联返回的是所有测试结果的总分第一阶段排名功能等级概况我们已在查询中指定,即。排名.个人资料myRankProfile. 在名单上排名我们已经指定了一个特性查询(LastonlineWeight)50,然后在我们使用的唯一排名表达式中引用:最后在线分数.它利用内置的等级特征新鲜度如果属性中的时间戳与当前时间戳相比是最近的,则该数字接近1。到目前为止还不错,没什么太棘手的。

与静态内容不同,此内容可能影响您是否应该看到它们。例如,他们可以喜欢你!我们可以索引一个加权集likedUserSet字段,该字段以键的形式保存用户喜欢的用户标识,以值的形式保存类似事件发生的时间戳。然后就可以直接过滤那些喜欢你的人(例如添加一个likedUserSet包含“777”子句),但我们如何在排名过程中纳入加权集信息?我们如何才能提高喜欢我们用户的用户?

在前面的结果中,排序表达式收益率这两次命中率均为0。使用者6888497210242094612实际上已经喜欢上用户了777.,但即使我们提供了“查询(incominglikewight)”:50.我们可以利用等级YQL中的函数(函数的第一个参数,也是唯一的第一个参数)秩()函数确定文档是匹配的,但所有参数都用于计算等级分数,然后使用a点积在我们的YQL排名子句中存储和检索原始分数(在这种情况下,当用户喜欢我们时的时间戳)如下:

{
“yql”:“从用户where!(用户ID包含\“777\”)中选择用户ID和摘要功能,并对其进行排名(lastOnline>1592486978,dotProduct(如EdUserSet,{“777\”:1}))限制2;”,
“排行”: {
“档案”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”,
“查询(IncomingLikeWight)”:“50”
}
},
“pos”:{
“半径”:“50mi”,
“ll”:“N40o44'22;W74o0'2”,
“属性”:“latLong”
},
“介绍”:{
“摘要”:“默认”
}
}
{
“根”:{
“id”:“顶级”,
“相关性”:1.0,
“字段”:{
“总数”:317
},
“覆盖范围”:{
“覆盖范围”:100,
“文件”:958,
“完整”:正确,
“节点”:1,
“结果”:1,
“结果完整”:1
},
“儿童”:[
{
“id”:“索引:用户/0/E8AA37DF0832905C3FA1DBD”,
“相关性”:98.97595807613169,
“来源”:“用户”,
“字段”:{
“userid”:6888497210242094612,
“suffilefeatures”:{
“rankingExpression(incomingLikeScore)”:50.0,
“rankingExpression(lastOnlineScore)”:48.97595807613169,
“vespa.summaryfeatures.cached”:0.0
}
}
},
{
“id”:“索引:用户/0/bde9bd654f1d5ae17fd9abc3”,
“相关性”:48.9787037037037,
“来源”:“用户”,
“字段”:{
“userid”:-5800469520557156329,
“suffilefeatures”:{
“rankingExpression(incomingLikeScore)”:0.0,
“rankingExpression(lastOnlineScore)”:48.9787037037037,
“vespa.summaryfeatures.cached”:0.0
}
}
}
]
}
}

现在是用户6888497210242094612已经提升到顶部,因为他们喜欢我们的用户和他们的用户收益率净全价值。当然,我们实际上有时间戳,当他们喜欢我们,所以我们可以在更复杂的表达式中使用它,但我们现在将保持简单。

这演示了如何通过排名框架对结果进行过滤和排名的机制。排名框架提供了一种灵活的方法,可以在查询时对点击数应用排名表达式(主要是数学表达式)。

如果我们想支持不同的路径并使dotProduct子句隐式地成为每个查询的一部分,该怎么办?这就是可定制Java容器层的作用所在——我们可以编写一个自定义搜索者组件。这使我们能够处理任意参数、重写查询并以特定方式处理结果。以下是Kotlin中的一个示例:

@之后(PhaseNames.TRANSFORMED\u查询)
class matchsearcher:searcher(){

伴侣对象{
//HTTP查询参数
val USERID\u QUERY\u PARAM=“USERID”

val属性\字段\用户\设置=“likedUserSet”
}

覆盖有趣的搜索(查询:查询,执行:执行):结果{
val userId=query.properties().getString(userId\u query\u PARAM)?.toLong()

//添加dotProduct子句
If(userId!=null){
val rankitem = query.model.querytree.getRankItem()
Val LikedUserSetClause = dotproductItem(attribute_field_liked_user_set)
likedUserSetClause.addToken(用户ID,1)
rankItem.addItem(likedUserSetClause)
}

//执行查询
trace(“YQL后面是:${query.yqlRepresentation()}”,2)
返回执行。搜索(查询)
}
}

那么在我们的services.xml文件我们可以按如下方式配置此组件:

...





http:// *:8080 / match

...

然后,我们只需构建和部署应用程序包,现在我们将查询到自定义处理程序时http://localhost:8080/match?userid=777:

{
“yql”:“从用户where!(用户ID包含\“777\”)中选择用户ID和摘要功能,排名(lastOnline>1592486978)限制2;”,
“排行”: {
“档案”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”,
“查询(IncomingLikeWight)”:“50”
}
},
“pos”:{
“半径”:“50mi”,
“ll”:“N40o44'22;W74o0'2”,
“属性”:“latLong”
},
“介绍”:{
“摘要”:“默认”
}
}

我们得到了与之前相同的结果!请注意,在Kotlin代码示例中,我们在修改后添加了一个跟踪以打印YQL表示,因此如果我们设置tracelevel = 2在URL参数中,响应还显示:

...
{
“消息”:“is后的YQL:从用户位置((排名(lastOnline>1592486978,dotProduct(LikeUserSet,{\'777\':1}))和!(用户ID包含\'777\”)限制2)选择用户ID、摘要功能
},
...

中间件Java容器层是通过搜索者或通过自定义结果的呈现渲染器.我们定制我们的搜索者要处理上述情况以及我们希望在搜索中隐含的其他方面。例如,我们支持的一个产品概念是“相互适合”的概念-您可能正在搜索具有特定偏好标准(如年龄范围和距离)的用户,但您还必须符合候选人的搜索条件搜索者组件我们可能会获取搜索用户的文档,以便在随后的扇出查询中提供他们的一些属性以进行筛选和排名。排名框架和自定义中间件层共同为我们提供了一种灵活的方式来支持我们的许多用例。在这些示例中,我们只讨论了几个方面,但有很多方面现有文件在这里.

它是如何建造并产生Vespa星团的

2019年春,我们开始制定建设这一新系统的计划。此时,我们还联系了Vespa团队,并定期就我们的用例咨询他们。我们的运营团队评估并构建了初始集群设置,后端团队开始在Vespa中记录、设计和原型化各种用例。

在OK188bet金宝搏官网CuPID中,后端系统是用Golang和C++编写的。为了在Vespa中编写自定义逻辑组件,以及通过使用Java Vespa HTTP Feed客户端API,我们必须稍微熟悉一点JVM环境-我们最终利用Kotlin定制Vespa组件和馈送管道。

从那时起,在多年的应用程序逻辑中进行了大量的移植,发现了Vespa中可能存在的问题,并在必要时咨询了Vespa团队。我们的匹配系统逻辑大部分是C++,所以我们还添加了逻辑来将当前的数据模型转换成分类的YQL查询,这些查询是通过REST向VESPA集群发布的。早些时候,我们还确保构建一个良好的管道,用完整的文档用户库重新填充集群;原型设计将涉及许多更改,以确定要使用的正确字段类型,并且无意中需要重新输入文档。

在我们构建我们的Vespa搜索集群时,我们需要确保两件事:它可以处理预期的搜索和写入流量,并且该系统服务的建议在现有匹配系统的质量上可相当。

在负载测试之前,我们到处都添加了Prometheus度量。胡蜂出口商提供大量的统计数据,Vespa本身也暴露一小组额外的额外韵律学.由此,我们围绕每秒查询、延迟、Vespa进程的资源使用等创建了各种Grafana仪表盘。我们还运行了胡蜂为了测试查询性能,在Vespa团队的帮助下确定,由于静态查询成本那是分组布局将为我们提供更高的吞吐量。在平面布局中,添加更多节点只会降低动态查询成本(即,查询部分取决于索引的文档数量)。分组布局意味着每个配置的节点组将包含完整的文档集,因此单个组可以用于查询。由于我们的静态查询成本很高,在保持节点计数不变的情况下,我们通过将组数从一个组的平面布局增加到三个组来提高吞吐量。最后,在对静态基准测试有了信心之后,我们还进行了实时影子流量测试。

然而,我们早期面临的最大障碍之一是提要性能。早期,我们甚至无法处理1000 QP的更新。我们大量使用加权集字段,但一开始这些字段性能不佳。幸运的是,Vespa团队及时帮助解决了这些问题以及数据分发方面的其他问题。从那时起,Vespa team还添加了关于喂养尺寸,其中许多我们在某种程度上雇用:在可能的大量加权集中的整数字段,允许通过设置批处理能见度延迟,利用很少的条件更新并使这些更新依赖于属性(即内存中的)字段,并通过压缩和合并提要管道中的操作来减少客户端往返。现在,管道可以在稳定状态下轻松地处理3K QPS,我们的小型集群可以在任何原因导致操作积压时处理11k QPS更新。

在我们确信集群能够处理负载后,我们需要验证建议的质量是否与现有系统一样好,如果不是更好的话。不可能完美地复制所有现有的行为,但是排名实施方式的任何微小偏差都会对gen产生巨大的影响建议的总体质量和总体生态系统。为此,我们应用了实验系统,一些测试组通过Vespa获得了建议,而对照组继续利用现有的匹配系统。我们分析了几个防御性商业指标,重申和修复问题,直到观察到Vespa组的结果,如不如比对照组结果更好。一旦我们对Vespa服务的结果充满信心,我们只需将建议查询路由到Vespa集群。我们能够将所有搜索流量交换到Vespa集群,而无需挂钩!

最后,新系统的简化架构概述如下所示:

Vespa现在做得怎么样,下一步是什么

让我们与我们的遗留系统相比,比较现在由Vespa支持的匹配系统的状态:

模式更新

  • 以前:一个日历周花在数百行代码更改上,并仔细协调多个子系统的部署
  • 之后:花几个小时向模式定义添加一个简单字段,并部署应用程序包

添加新排序

  • 之前:部署花费半天时间
  • 之后:排名表达式也是模式定义的更新,可以部署到实时系统。这意味着只需要几秒钟才能生效!

缩放和维护

  • 以前:为实现高可用性,需要花费数周时间手动分发碎片和放置生产服务运行文件
  • 之后:只需将新节点添加到配置文件,Vespa会自动分发数据以满足所需的冗余级别。我们的大部分操作不需要任何人工干预或重新启动任何有状态节点

总的来说,Vespa集群的开发和维护对于OkCupid的大部分产品路线图来说都是一个福音。自2020年1月底以来,我们已经生产了我们的Vespa集群,并通过它提供我们的所有建议。我们还添加了数188bet金宝搏官网十个支持主要产品的新字段、排名表达式和用例今年发布的内容如下堆栈.与我们以前的匹配系统不同,我们现在使用的是实时查询的机器学习模型。

对我们来说,Vespa最大的卖点之一是它直接支持使用张量进行排名,并与使用类似的框架训练的模型集成张量流.这种能力是我们希望在未来几个月继续杠杆的主要特色之一。我们已经使用了某些用例的张量,我们很兴奋,很快就会介绍一体化的机器学习模型,以便更好地预测结果并匹配我们的用户。

此外,Vespa最近发布了对高维近似最近邻索引的支持,这些索引是完全实时的、可并发搜索的和可动态更新的。我们期待着探索其他具有实时性的用例最近的邻居搜索。

188bet金宝搏官网OkCupid x Vespa。发货!

很多人都听说过Elasticsearch或使用过Elasticsearch,但在Vespa周围没有那么大的社区。我们相信,使用Elasticsearch构建的许多其他应用程序都可以更好地使用Vespa。Vespa与OkCupid的用例非常匹配,我们很高兴我们进行了这项投资。这个新的archit“体系结构”使我们能够更快地移动,更快地提供新功能。我们的团队相对较小,因此不必担心操作复杂性也很好。现在我们更倾向于横向扩展搜索能力。我们肯定无法在未来几年取得进展去年没有Vespa。有关Vespa的更多信息和技术功能,请务必查看188bet金宝搏官网使用Vespa AI进行电子商务搜索和推荐通过@jobergum..

我们采取了第一步,喜欢并向Vespa团队发送了一条信息。他们回复了我们,这是一场比赛!没有Vespa团队的帮助,我们不可能做到这一点。特别感谢@jobergum.@盖斯特为查询和排名提供指导,并为@kkraune.@vekterli.感谢他们的支持。团队为我们提供的支持和努力水平真的很棒,从深入挖掘我们的用例到诊断性能问题,再到在短时间内增强Vespa引擎。@vekterli.甚至飞到我们在纽约的办公室,直接与我们合作一周,以确保我们的集成和用例能够得到满足。非常感谢Vespa的团队!

最后,我们只讨论了Vespa使用的几个方面,但如果没有我们的后端和操作团队在过去一年中所做的大量工作,这一切都不可能实现。在弥合现有系统和更现代的技术之间的差距方面,我们面临着许多独特的挑战,但这些都是另一个时代的博客文章。

如果您对Okcupid的后端团队的挑战很感兴趣,188bet金宝搏官网我们正在招聘!

188bet金宝搏官网OkCupid技术博客

阅读OkCupid工程团队每天联系数百万人的188bet金宝搏官网故事

188bet金宝搏官网OkCupid技术博客

188bet金宝搏官网Okcupid的工程团队负责每天匹配数百万人。在Okcupid Tech Blog上阅读他们的故事188bet金宝搏官网

188bet金宝搏官网OkCupid技术博客

188bet金宝搏官网Okcupid的工程团队负责每天匹配数百万人。在Okcupid Tech Blog上阅读他们的故事188bet金宝搏官网