胡蜂属与Elasticsearch匹配数以百万计的人

交友应用,不可或缺的一部分经历巨大的潜力为您推荐匹配的基础上,无数的喜好你和你的潜在的匹配设置。你可以想象,有许多激励优化这部分的经验是第一步,每个人都开始在比赛之前,对话和超越。
你的偏好,但并不是唯一因素如何向您推荐潜在的匹配推荐其他潜在的匹配(或你)。如果我们简单地显示所有符合你的标准的用户没有任何形式的排名,最终的结果将是不匹配的方法。举个例子,如果我们不试着把用户的最近的活动结果,会有更高的机会你花更多的时间与人交流最近没有使用这个应用程序。这当然不设置用户成功!除了简单地喜好你和其他人,我们利用大量的算法和因素推荐用户,我们认为你应该看到。
当服务建议我们需要提供最好的结果在那个时间点,让你不断看到更多的建议或传递你的潜在的匹配。在其他应用程序内容本身可能不会经常改变或少这样的及时性是至关重要的,这可以通过离线系统,再生这些建议。例如,当使用Spotify的每周“发现”功能你可以享受一系列的推荐歌曲但这集是冻结,直到下周。OkCupid的情况下,我们允188bet金宝搏官网许用户不断地实时查看自己的建议。我们推荐的“内容”——我们的用户,在本质上是高度动态的(例如,一个用户可以加入,改变他们的偏好,概要文件的细节,位置,随时关闭,等等),可以改变和他们应该如何推荐谁,所以我们要确保潜在的匹配你看到一些最好的建议,你可以看到在那个时间点上。
利用各种排名算法虽然能够连续实时提供建议,我们需要使用一个搜索引擎,不断保持最新的用户数据和提供过滤和排名潜在候选人的能力。
什么问题现有的匹配系统
188bet金宝搏官网OkCupid多年来一直使用一个自定义内部匹配系统。我们不会进入完整的细节匹配系统,但在高级别上,想象一个使用映射-规约模式框架的碎片与每个碎片包含用户空间内存的一部分相关的用户数据,用于加工各种过滤器和动态。搜索所有碎片和扇出最终结果合并返回顶部k的候选人。这个定制的匹配系统对团队起到了积极的作用,那么为什么我们现在决定改变这个系统吗?
支持各种推荐项目在未来几年随着团队的成长,我们知道我们需要修改这个系统。最大的难点之一是发展等模式更新添加新数据对用户(例如用户偏爱的性别标签)要求数百数千行仔细的协调,确保所需的样板代码和部署系统的所有部分被部署以正确的顺序。只是试图添加一个新的方法来过滤用户设置或添加一个新的方法排序结果的工程师需要半天的时间手动部署到每一个碎片在生产和保持通知可能出现的问题;回滚没有更快。更重要的是,它变得很难操作和规模系统自碎片和副本是手动分配和分布在裸露的金属机器的舰队。在2019年初随着负载匹配系统的增加,我们需要提高搜索能力添加另一个副本集手动放置在多台计算机服务实例——一个多周后端和操作团队之间的工作。这个时候我们也开始注意到内部建立了服务发现系统性能瓶颈,消息队列,等等。而这些组件之前适合这家公司,我们是在负载达到一个点,我们不确定是否其中一个子系统自己可能规模。我们有目标,我们更多的工作负载转移到云环境和改变匹配系统,本身一个艰难的任务,也需要带上所有这些其他子系统组件。
今天在OkCupi188bet金宝搏官网d这些子系统提供更健壮的OSS云友好选项和团队已经在过去的两年里采取了各种不同的技术取得了巨大的成功。我们不会谈论这些努力在这个博客上,而是集中在解决上述问题的努力,我们已经包括通过移动开发者更友好的和可扩展的搜索引擎对我们的建议:胡蜂属。
这是一个比赛!为什么和188bet金宝搏官网胡蜂属OkCupid匹配吗
历史OkCupid一直是一188bet金宝搏官网个小团队,并且我们知道早就解决的核心搜索引擎将是非常困难和复杂所以我们看着开源选项我们可以支持我们的用例。两大竞争者Elasticsearch和胡蜂属。
Elasticsearch
这是一个受欢迎的选择与一个大社区,文档和支持。有许多功能,甚至使用易燃物。的开发经验,我们可以添加新的schema字段映射,查询可以通过结构化的REST调用,有一些支持查询时的排名,编写自定义插件的能力,等扩展和维护时,只需要确定碎片的数量和系统处理分布的副本给你。扩展需要重建另一个指数较高的碎片数量。
最大的一个原因我们选择Elasticsearch是缺乏真正的内存部分更新。这是非常重要的对于我们的用例,因为文件我们将索引,我们的用户,需要经常更新通过喜欢/传球,消息,等。这些文件在本质上是高度动态的,而内容如广告或图像大多是静态对象与属性变化很少,所以效率低下的读写周期更新是主要的表现也让人担忧。
胡蜂属
这是开源仅仅几年前声称支持存储、搜索排名,和组织大数据在用户服务时间。胡蜂属支持
- 高饲料性能通过真正的内存部分更新,而不需要对整个文档进行检索(据报道,每秒40-50k更新每个节点)
- 排名提供了一个灵活的框架允许在查询时处理
- 直接支持集成机器学习模型(例如TensorFlow)排名
- 查询可以通过表达YQL查询语言(雅虎)的REST调用
- 自定义逻辑通过Java组件的能力
扩展和维护时,您从来没有想到碎片了——你配置你的内容节点的布局和胡蜂属自动处理分割文档放入桶中,复制,分发数据。此外,数据自动恢复和重新分配副本每当你添加或删除节点。扩展仅仅意味着更新配置添加节点,让胡蜂属自动重新分配这些数据生活。
整体胡蜂属似乎最好的支持我们的用例。188bet金宝搏官网OkCupid包含很多不同的信息用户,帮助他们找到最佳匹配——的过滤和排序有超过100的!我们总是会添加更多的过滤和排序,所以能够支持工作流是重要的。在写和查询,胡蜂属是最类似于我们现有的匹配系统;这是我们匹配系统,还需要快速的内存部分更新和实时处理在查询时间排名。胡蜂属排名还有一个更加灵活和简单的框架;表达的能力在YQL查询而不是尴尬Elasticsearch查询的结构是另一种不错的奖金。扩展和维护,胡蜂属的自动数据分布能力非常吸引我们的团队规模相对较小。总之看来胡蜂属将提供我们更好地支持用例和性能需求,Elasticsearch相比,更容易维护。
Elasticsearch更广为人知,我们可以学习易燃物的使用它,但要么选择需要大量的前期研究和调查。胡蜂属已经为许多生产使用情况下,ZedgeFlickr提供数十亿的图片,雅虎双子座广告平台拥有超过每秒十万个请求服务广告每月10亿活跃用户。这给了我们信心,这是一个测试,性能,和可靠的选择——事实上,胡蜂属的起源在长时间Elasticsearch。
此外胡蜂属团队一直积极参与和帮助。胡蜂属最初是用来提供广告和内容页,据我们所知它尚未用于一个交友平台。我们最初使用的胡蜂属挣扎,因为它是这样一个独特的用例,但是胡蜂属团队已经超级响应和快速优化系统来帮助我们处理出现的一些问题。
胡蜂属是如何工作和OkCupid搜索是什么样子的188bet金宝搏官网

在我们深入胡蜂属用例,这是一个快速概述胡蜂属是如何工作的。胡蜂属是一个集众多服务但每个集装箱码头工人可以配置为履行管理/配置节点的角色,一个无状态的Java容器节点,和/或一个有状态的c++内容节点。一个应用程序包包含配置、组件毫升模型等可以通过部署州API配置集群,集群处理应用更改容器和内容。饲料请求和查询所有通过无状态的Java容器通过HTTP(它允许定制的处理),之前在集群或查询内容提要更新土地扇出的内容层分布式查询执行发生。在大多数情况下,部署一个新应用程序包只需要几秒钟,胡蜂属处理这些变化生活在容器和内容集群,这样你很少需要重启。
搜索是什么样子?
我们维护的文档胡蜂属集群包含大量的属性对一个给定的用户。的模式定义定义文档类型的字段以及等级资料包含一组适用的排名表达式。假设我们有一个模式定义代表一个用户就像这样:
搜索用户{
文档用户{
字段标识类型长{
索引:总结|属性
属性:快速搜索
排名:过滤器
}
字段latLong类型位置{
索引:属性
}
# UNIX时间戳
长整型字段lastOnline {
索引:属性
属性:快速搜索
}
#包含该用户文档的用户喜欢
#和相应的权重是UNIX时间戳时发生
字段likedUserSet类型weightedset <时间> {
索引:属性
属性:快速搜索
}
}
rank-profile myRankProfile继承默认{
rank-properties {
查询(lastOnlineWeight): 0
查询(incomingLikeWeight): 0
}
函数lastOnlineScore () {
表达式:查询(lastOnlineWeight) *新鲜(lastOnline)
}
函数incomingLikeTimestamp () {
表情:rawScore (likedUserSet)
}
函数hasLikedMe () {
表达:如果(incomingLikeTimestamp > 0 1 0)
}
函数incomingLikeScore () {
(incomingLikeWeight) * hasLikedMe表达式:查询
}
第一阶段{
{表达
lastOnlineScore + incomingLikeScore
}
}
总结特性{
lastOnlineScore incomingLikeScore
}
}
}
的索引:属性
指定显示这些字段应该保持内存允许我们得到最好的这些字段的读写性能。
假设我们密集的集群的用户文档。我们可以做一个搜索过滤和排序的字段。例如,我们可以做一个POST请求的默认搜索处理程序http://localhost: 8080 /搜索/
找到用户,除了我们自己的用户777年
从我们的位置在50英里,一直以来在线时间戳1592486978
在最近的活动,保持最高的两名候选人。也让我们选择summaryfeatures帮助我们看到每个排名的贡献表达,我们已经在我们的排名简介:
{
“yql”:“从用户选择userId, summaryfeatures lastOnline > 1592486978和! (userId包含\“777 \”)限制2;”,
“排名”:{
“简介”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”
}
},
" pos ": {
“半径”:“50米”,
“我”:“N40o44 22; W74o0 2”,
“属性”:“latLong”
},
“演讲”:{
“摘要”:“默认”
}
}
我们可能会得到这样的结果:
{
"根":{
“id”:“最高级的”,
“相关性”:1.0,
“字段”:{
“totalCount”: 317
},
“覆盖”:{
“覆盖”:100年,
“文件”:958年,
“全”:没错,
“节点”:1、
“结果”:1、
“resultsFull”: 1
},
“孩子”:(
{
“id”:“指数:用户/ 0 / bde9bd654f1d5ae17fd9abc3”,
“相关性”:48.99315843621399,
“源”:“用户”,
“字段”:{
“标识”:-5800469520557156329,
" summaryfeatures ": {
“rankingExpression (incomingLikeScore)”:0.0,
“rankingExpression (lastOnlineScore)”:48.99315843621399,
“vespa.summaryFeatures。缓存”:0.0
}
}
},
{
“id”:“指数:用户/ 0 / e8aa37df0832905c3fa1dbbd”,
“相关性”:48.99041280864198,
“源”:“用户”,
“字段”:{
“标识”:6888497210242094612,
" summaryfeatures ": {
“rankingExpression (incomingLikeScore)”:0.0,
“rankingExpression (lastOnlineScore)”:48.99041280864198,
“vespa.summaryFeatures。缓存”:0.0
}
}
}
]
}
}
在匹配滤波后击中第一阶段排名表达式是用来衡量。的相关性
返回的总体评分结果第一阶段排名功能的rank-profile我们中指定的查询,即。ranking.profile
myRankProfile
。的列表中ranking.features
我们指定的一个特性查询(lastOnlineWeight)
50,然后只排名表达式中引用我们使用:lastOnlineScore
。,使用一个内置的排序功能新鲜
是一个数接近1如果最近的时间戳属性相比,当前的时间戳。到目前为止一切顺利,没什么太复杂的。
与静态内容,这些内容可以影响他们是否应该被你。例如,他们可以喜欢你!我们可以指数加权集likedUserSet
字段在每个用户文档保存键他们喜欢的用户id值等发生的时间戳。将简单过滤那些喜欢你(如添加一个likedUserSet包含\“777 \”
YQL条款),但是我们如何整合期间加权集信息的排名?我们怎么可能会增加一个用户,喜欢我们的用户?
排名在前面的结果表达式incomingLikeScore
这两个命中是0。用户6888497210242094612
实际上喜欢用户777年
,但这不是目前在排名访问,即使我们有提供“查询(incomingLikeWeight)”:50
。我们可以利用排名YQL函数(第一,只有第一个论点的排名()
函数确定是否匹配一个文档,但所有参数用于计算等级分数),然后使用dotProduct在我们的YQL排名条款来存储和检索原始分数(在本例中,当用户喜欢我们的时间戳)如下:
{
“yql”:“从用户选择userId, summaryfeatures ! (userId包含\“777 \”)和等级(lastOnline > 1592486978, dotProduct (likedUserSet,{\“777 \”: 1}))限制2;”,
“排名”:{
“简介”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”,
“查询(incomingLikeWeight)”:“50”
}
},
" pos ": {
“半径”:“50米”,
“我”:“N40o44 22; W74o0 2”,
“属性”:“latLong”
},
“演讲”:{
“摘要”:“默认”
}
}{
"根":{
“id”:“最高级的”,
“相关性”:1.0,
“字段”:{
“totalCount”: 317
},
“覆盖”:{
“覆盖”:100年,
“文件”:958年,
“全”:没错,
“节点”:1、
“结果”:1、
“resultsFull”: 1
},
“孩子”:(
{
“id”:“指数:用户/ 0 / e8aa37df0832905c3fa1dbbd”,
“相关性”:98.97595807613169,
“源”:“用户”,
“字段”:{
“标识”:6888497210242094612,
" summaryfeatures ": {
“rankingExpression (incomingLikeScore)”:50.0,
“rankingExpression (lastOnlineScore)”:48.97595807613169,
“vespa.summaryFeatures。缓存”:0.0
}
}
},
{
“id”:“指数:用户/ 0 / bde9bd654f1d5ae17fd9abc3”,
“相关性”:48.9787037037037,
“源”:“用户”,
“字段”:{
“标识”:-5800469520557156329,
" summaryfeatures ": {
“rankingExpression (incomingLikeScore)”:0.0,
“rankingExpression (lastOnlineScore)”:48.9787037037037,
“vespa.summaryFeatures。缓存”:0.0
}
}
}
]
}
}
现在用户6888497210242094612
被提高到为他们喜欢我们的用户和他们incomingLikeScore
网的全部价值。当然,我们有时间戳时喜欢我们,所以我们可以利用更复杂的表达式,但我们会保持简单。
这说明如何过滤和排序结果的机制通过排名框架。排名框架提供了一种灵活的方式来应用排名表达式(大多只是数学)在查询时。
定制Java中间件层
如果我们想支持不同的路径和dotProduct条款隐含每个查询的一部分吗?这就是定制的Java容器层——我们可以编写一个定制的搜索者组件。这让我们处理任意参数,重写查询,并以特定的方式处理结果。这里有一个例子在芬兰湾的科特林:
@After (PhaseNames.TRANSFORMED_QUERY)
类MatchSearcher:搜索器(){
伴星{
/ / HTTP查询参数
val USERID_QUERY_PARAM = "标识"
val ATTRIBUTE_FIELD_LIKED_USER_SET = " likedUserSet "
}
覆盖有趣的搜索(查询:查询,执行:执行):结果{
val userId = query.properties () .getString (USERID_QUERY_PARAM) ? .toLong ()
/ /添加dotProduct条款
如果(userId ! = null) {
val rankItem = query.model.queryTree.getRankItem ()
val likedUserSetClause = DotProductItem (ATTRIBUTE_FIELD_LIKED_USER_SET)
likedUserSetClause。addToken (userId, 1)
rankItem.addItem (likedUserSetClause)
}
/ /执行查询
查询。跟踪(“YQL后是:$ {query.yqlRepresentation ()} ", 2)
返回execution.search(查询)
}
}
然后在我们的services . xml文件我们可以配置组件如下:
…
<搜索>
<链id =“默认”继承= "胡蜂属" >
<搜索器id = " com.okc188bet金宝搏官网upid.match。MatchSearcher "包= " match-searcher " / >
< /链>
< /搜索>
<处理程序id =“默认”包= " match-searcher " >
<绑定> http:// *: 8080 /匹配< /绑定>
< /处理程序>
…
然后我们简单地构建和部署应用程序包,现在当我们做一个查询,自定义处理程序http://localhost: 8080 /匹配?用户id = 777
:
{
“yql”:“从用户选择userId, summaryfeatures ! (userId包含\“777 \”)和等级(lastOnline > 1592486978)限制2;”,
“排名”:{
“简介”:“myRankProfile”,
“功能”:{
“查询(lastOnlineWeight)”:“50”,
“查询(incomingLikeWeight)”:“50”
}
},
" pos ": {
“半径”:“50米”,
“我”:“N40o44 22; W74o0 2”,
“属性”:“latLong”
},
“演讲”:{
“摘要”:“默认”
}
}
之前我们得到同样的结果!注意,在芬兰湾的科特林的代码示例中,我们添加了一个跟踪我们修改后打印YQL表示如果我们集tracelevel = 2
在URL参数,反应还显示:
…
{
“消息”:“YQL后是:选择userId, summaryfeatures从用户(排名(lastOnline > 1592486978, dotProduct (likedUserSet,{\“777 \”: 1}))和! (userId包含\“777 \”)限制2;”
},
…
Java容器中间件层是一种强大的方法来添加自定义逻辑处理通过搜索者或自定义渲染的结果通过渲染器。我们定制搜索者处理类似上述和其他方面,我们想让隐含在搜索。例如,我们支持一个产品概念是“相互配合”的想法,你可能会寻找用户提供特定的偏好标准(如年龄和距离),但你也必须符合候选人的搜索条件。我们支持这样一个用例搜索者组件我们可能获取搜索用户的文档提供他们的一些属性在随后的扇出查询过滤和排名。排名框架和自定义中间件层一起为我们提供了一个灵活的方式来支持我们的许多用例。这些示例中我们只涉及几个方面但有丰富的文档在这里。
如何去构建和productionizing胡蜂属集群
在2019年的春天我们开始散列计划建造出这个新系统。这个时候我们还伸出胡蜂属团队,定期咨询他们对我们的用例。我们的运营团队估计,建立一个初始集群设置和后端团队开始记录,设计和原型在胡蜂属不同的用例。
早期原型阶段
OkC188bet金宝搏官网upid的后端系统是用Golang和c++编写的。为了编写自定义逻辑组件胡蜂属以及确保高饲料饲料使用Java胡蜂属HTTP客户端API,我们不得不稍微熟悉JVM环境——我们最终利用芬兰湾的科特林在定制胡蜂属组件和进料管道。
从那里,这是多年来大量的移植的应用程序逻辑和胡蜂属发现什么是可能的,必要时咨询胡蜂属团队。我们的匹配系统的逻辑是在c++中我们还增加了逻辑将我们目前的过滤和排序数据模型转化为等价的YQL查询,我们通过其他问题胡蜂属集群。在早期我们也确保构建好管道重新繁衍集群文件的全部用户群;原型将涉及许多变化来确定正确的字段类型使用,无意中需要重新喂料文件。
监控和负载测试
当我们建立了胡蜂属搜索集群,我们需要确保两件事:它可以处理预期搜索和写流量,建议由这个系统质量可比现有匹配系统。
在负载测试之前,我们添加了普罗米修斯指标无处不在。Vespa-exporter提供了大量的统计数据和胡蜂属本身也暴露出少量额外的指标。从这个我们创造了各种Grafana仪表板大约每秒查询,延迟,胡蜂属资源使用的过程,等我们也跑了vespa-fbench测试查询性能和胡蜂属团队的帮助下确定,由于相对较高静态查询成本,一个分组的布局将为我们提供更高的吞吐量。在一个平面布局,添加更多的节点只会主要是减少动态查询成本(即查询的一部分取决于文档索引)的数量。分组的布局配置意味着每个组的节点将包含完整的文档集,因此一个集团可以查询。由于我们高静态查询成本,同时保持我们的节点数相同的我们的吞吐量更增加了增加有效的群体数量从平面布局一组三组。最后,我们也表现生活的影子流量测试后,我们得到了静态基准的信心。
性能优化
我们面临的最大障碍之一,然而,在饲料性能。在早期我们甚至无法处理1000每秒的更新。但是这些我们有大量利用加权集字段没有性能。幸运的是胡蜂属团队及时帮助解决这些问题,以及周围的数据分布。此后胡蜂属团队还添加了丰富的文档饲料分级在某种程度上,其中许多我们雇佣:整数字段大型加权集在可能的情况下,允许批处理设置visibility-delay
,利用一些条件更新和那些依赖属性(即内存)字段,并减少客户往返给水管道通过压缩和合并操作。现在管道轻松处理3 k每秒在稳态和温和的集群已经观察到处理11 k每秒更新当有积压的操作由于某种原因。
推荐质量
我们有信心后,集群可以处理负载,我们需要验证建议的质量一样好,如果不是比现有系统。无法完全复制所有现有的行为,然而,任何微小的偏差在排名是如何实现的巨大影响的一般质量的建议和整个生态系统。我们应用实验系统,一些测试组通过胡蜂属获得推荐,而对照组继续利用现有的匹配系统。我们分析几个防御业务指标,重申和解决问题直到胡蜂属组观察到的结果是一样好,如果不是比,结果在对照组。一旦我们有信心的结果由胡蜂属,我们只是必须推荐查询路由到胡蜂属集群。我们可以交换所有搜索流量胡蜂属集群顺利!
系统图
最后,一个简化的新系统是这样的体系结构概述:

胡蜂属是如何做的,接下来是什么
让我们来比较一下现在的状态匹配系统支持的胡蜂属相比我们的遗留系统:
模式更新
- :一个日历星期花在数百行代码更改,与多个子系统和精心协调部署
- :几个小时后添加一个简单的模式定义字段,并部署应用程序包
添加一个新的类
- :半天用于部署
- 后:排名表达式也更新模式定义,可以部署到活动的系统。这意味着只需要几秒生效!
扩展和维护
- :多周努力生产手动分发碎片和位置服务运行文件来实现高可用性
- 后:将新节点添加到配置文件和胡蜂属自动分发数据,以满足所需的冗余水平。大部分我们的操作不需要任何人工干预或重新启动任何状态的节点
胡蜂属集群的整体开发和维护方面是有利于OkCupid的产品路线图。188bet金宝搏官网2020年1月结束以来,我们productionized胡蜂属集群和服务我们所有的建议。我们还添加了许多新领域,排名表达式和用例支持等主要产品发布今年栈。与我们之前的匹配系统,我们现在住在查询时使用机器学习模型。
接下来是什么?
为我们胡蜂属的一个最大的卖点是它的直接支持排名与张量和集成模型训练与框架TensorFlow。这种能力的一个主要功能是我们希望在未来几个月继续利用。我们已经利用张量在特定情况下,我们很快就兴奋地看着集成更多的机器学习模型,我们希望将更好地预测结果和匹配我们的用户。
此外,胡蜂属最近发布了支持高维近似最近邻的索引是完全实时、并行搜索,动态更新的。我们期待着与实时探索其他用例最近的邻居搜索。
188bet金宝搏官网OkCupid x胡蜂属。船吧!
很多人都听说过或与Elasticsearch合作,但是没有那么大胡蜂属周围社区。我们相信有很多其他应用程序构建和胡蜂属Elasticsearch那将是更好的。胡蜂属OkCupid的用例是一个伟大的比赛,我们很高兴,我们做了投188bet金宝搏官网资。这个新的体系结构允许我们加快并交付新功能更迅速。我们一个相对较小的团队也很好不必太多担心操作复杂性。现在我们更准备水平范围内搜索能力。我们肯定没有能够在过去的一年里我们已经取得的进展没有胡蜂属。更多的胡蜂属信息和技术能力,一定要检查电子商务与胡蜂属人工智能搜索和推荐通过@jobergum。
我们喜欢的第一步和胡蜂属团队发送一条消息。他们给我们,这是一个比赛!我们不可能这样做没有胡蜂属团队的帮助。特别感谢@jobergum和@geirst提供指导查询和排名和一个超级特殊shoutout@kkraune和@vekterli所有他们的支持。支持水平和工作团队提供我们真是太棒了,从深入研究我们的用例诊断性能问题在短时间内增强胡蜂属引擎。@vekterli甚至飞出我们的办公室在纽约,直接与我们工作了一个星期,以确保我们的集成和用例可以满足。非常感谢你的团队胡蜂属!
最后,我们只涉及几个方面对我们使用胡蜂属,但这一切都是不可能的工作由我们后台和运营团队在过去的一年。有很多独特的挑战我们之间的鸿沟在现有系统和更现代的技术堆栈但这些博客文章另一个时间。
如果你有兴趣挑战后端团队OkCupid工作,188bet金宝搏官网我们正在招聘!
最初发表在https://tech.188bet金宝搏官网okcupid.com2020年9月29日。