如果您正在使用 rails/ruby 开发您的 API,您可以查看 searchkick,它可以让搜索解决方案每天变得更智能且使用更多。
现在,如果您不在轨道上,或者您想开发自己的内部实现,这里有一些我对架构的建议。
让我们首先从基本概述、关键模块、缺点和针对解决方案中的缺点调整架构开始。
你需要
1) 评分算法,您可以在其中为公式定义一个方程式,该公式将为每个文档生成分数。
让我们考虑你提到的参数
a) 每个文档被显示的次数
b) 文档被点击的次数。
c) 查询搜索的文档。
现在你还没有提到 a) 和 b) 如何适应当前的上下文。我会假设一个更简单的,但如果你想构建一个非常先进的智能解决方案,我也会将 a) b) 与 c) 结合起来。例如 - 文档针对给定关键字出现了多少次。像我一样搜索“雪地靴”应该考虑这一点(出现次数/点击次数),仅当查询或多或少像“雪地靴”时才适用于所有情况。其中“雪地靴”可以分解为具有以下元的关键字,关键字顺序接近。
{
"keyword": "snow",
"document_ids": [3, 5, 6, 8],
"document_ids_views": [{
"doc_id": 3,
"views ": 110,
"clicks": 560
}, {
"doc_id": 5,
"views": 100,
"clicks": 78
}, {
"doc_id": 6,
"views": 100,
"clicks": 120
}, {
"doc_id": 3,
"views": 100,
"clicks": 465
}]
}
{
"keyword": "boots",
"document_ids": [3, 5, 6, 8],
"document_ids_views": [{
"doc_id": 3,
"views ": 100,
"clicks": 56
}, {
"doc_id": 5,
"views": 100,
"clicks": 78
}, {
"doc_id": 6,
"views": 100,
"clicks": 120
}, {
"doc_id": 3,
"views": 100,
"clicks": 465
}]
}
以上是每个关键字存储在单独数据库中的聚合数据。
像这样,我将每天在单独的数据存储中构建统计元数据,比如说 mongo。如果我的元数据中已经有“雪”,并且新的查询带有这个关键字,我会更新相同的元文档。
现在我想讨论一下缺点以及为什么我选择将它们保存在单独的数据库中而不是将它们附加到 elasticsearch 文档中。
我不想每次触发新查询以更新弹性文档中的点击计数和视图计数时都使用 elasticsearch 集群,因为我知道使用倒排索引合并,更新的 I/O 非常广泛。
现在为了弥补这个缺点,我将每天或每两天进行一次批处理作业,以将这些元信息移植到弹性文档中。我会用这个新的元信息重建整个集群,并将别名从旧索引移动到新索引,而不会停机。
现在要将此信息关联或添加到弹性文档,我将使用parent-child documents relationship 将弹性文档映射到与此关联的关键字。
所以我的基本父文档和子文档看起来像
父文档
PUT /index/type/3
{
"name": "Reebok shoes",
"category": "snow boots",
"price": 120
}
子文档
PUT /index/type_meta/1?parent=3
{
"keyword": "boots",
"document_id": 3,
"doc_id": 3,
"views ": 100,
"clicks": 56
}
PUT /index/type_meta/1?parent=3
{
"keyword": "snow",
"document_id": 3,
"doc_id": 3,
"views ": 110,
"clicks": 560
}
上面的父子文档几乎解释了我是如何为每个文档的搜索统计构建元数据的。
到目前为止,我们已经构建了一个非常智能的解决方案来收集搜索统计的事件数据,并成功地将它们与弹性中的每个文档相关联。
让我们从这里开始查看评分查询 -
我不会在这里深入设计评分算法,但我会更多地实现查询,它可以根据视图对文档进行评分,点击与关键字相关联以及与关键字相关。
Function score query
Script score
现在我可以选择在名称上比在类别上更重视匹配。从您的用例的角度来看,这就是全部内容,我不会深入为您设计分数公式。
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"boost": "5",
"functions": [{
"filter": {
"match": {
"name": "snow"
}
},
"random_score": {},
"weight": 200
}, {
"filter": {
"match": {
"name": "boots"
}
},
"weight": 200
}, {
"filter": {
"match": {
"category": "snow"
}
},
"random_score": {},
"weight": 100
}, {
"filter": {
"match": {
"category": "boots"
}
},
"weight": 100
}, {
"filter": {
"query": {
"has_parent": {
"type": "type_meta",
"query": {
"match": {
"keyword": "snow"
}
}
}
}
},
"script_score": {
"script": {
"lang": "painless",
"inline": "_score + 20*doc['clicks'].value + 40 * doc['views].value"
}
}
}, {
"filter": {
"query": {
"has_parent": {
"type": "type_meta",
"query": {
"match": {
"keyword": "boots"
}
}
}
}
},
"script_score": {
"script": {
"lang": "painless",
"inline": "_score + 20*doc['clicks'].value + 40 * doc['views].value"
}
}
}],
"score_mode": "max",
"boost_mode": "multiply"
}
}
}
因此您可以使用与上述类似的查询,我刚刚为每个子句选择了一个带有演示提升参数的非常简单的公式,并且可以在实施高级评分算法时重构此查询。
脚本评分功能在这里很重要,因为我首先根据单个父文档的搜索关键字过滤子文档,然后使用脚本评分来使用点击和查看计数来影响我的整体文档评分。
现在这是我希望在我的项目中实施的一种解决方案,我愿意为我的解决方案提出建议和改进。
请分享您的建议和改进。
希望这会有所帮助
谢谢