【独家深度】Gitlab基于Elasticsearch的站内搜索设计

这篇具有很好参考价值的文章主要介绍了【独家深度】Gitlab基于Elasticsearch的站内搜索设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

通过分析Gitlab的站内搜索设计,借鉴其设计经验,来改进自己的站内搜索方案,包括领域对象划分,索引设计,权限控制设计。

这可能是国内第一篇详细解剖Gitlab站内搜索设计实现的文章。

基础背景

Gitlab的免费版本采用的是Postgresql的FTS(full text search)进行搜索。

Gitlab的白金版本才支持基于Elasticsearch的高级搜索(可以申请30天的试用license体验)

Gitlab的领域对象关系

【独家深度】Gitlab基于Elasticsearch的站内搜索设计,搜索,gitlab,elasticsearch,搜索引擎

Gitlab的索引设计

gitlab的ES索引结构

gitlab会在ES内部建立如下索引

  • gitlab-production
  • gitlab-production-commits
  • gitlab-production-issues
  • gitlab-production-merge_requests
  • gitlab-production-migrations
  • gitlab-production-notes
  • gitlab-production-users
gitlab-production是Project的索引

整个索引的mapping采用大宽表的设计,不同领域对象的字段都会平铺在一起,具有相同的document-type  (ES6/Lucene7已经解决大宽表下sparse field所导致的空间浪费问题 Space Saving Improvements in Elasticsearch 6.0 | Elastic Blog)

(该索引有 project, blob, milestone, snippet, wiki_blob 领域对象)(blob是Binary Large Object的意思,这里特指Code)

它通过join的方式,将project和它的子对象建立父子关系,以便于做到权限控制

        "join_field" : {
          "type" : "join",
          "eager_global_ordinals" : true,
          "relations" : {
            "project" : [
              "note",
              "blob",
              "issue",
              "milestone",
              "wiki_blob",
              "commit",
              "merge_request"
            ]
          }
        }

关于索引的设计取舍,参考2019年gitlab的分享。

https://download.csdn.net/download/yyw794/88646899

注意:

根据Gitlab的官方描述,他们是更倾向不同领域对象采用不同的索引设计,因为搜索速度更快,重建索引更快等(也是ES官方推荐的方式)。

Gitlab采用不同领域对象放在1个索引里,仅仅是为了解决权限控制的问题。

子索引分析

索引字段一般分为3部分:

  • 对象id信息(用于唯一定位)
  • 对象基本信息(用于搜索和显示)
  • 对象父信息(用于权限控制)

以issue的索引gitlab-production-issues 为例:

对象id信息部分

issue对象包含子对象task(work_item)

索引采用的是平铺的设计层次。

每个对象包含id和iid(子对象id)和type(对象类型)3个值,来唯一定义这个对象。

对象基本信息

(略)

对象父信息

每个子索引的对象都有3个字段进行权限控制:

  • visibility_level(project的权限,父父对象权限)
  • xxx_access_level(父对象的权限 )
  • 父对象id

备注:

子对象不一定都有id,例如commit对象,就采用唯一的sha作为id(_id=${project_id}_${sha})

merge_request索引虽然有iid,但是没有发现其有子对象

merge_request有hashed_root_namespace_id字段

数据同步

【独家深度】Gitlab基于Elasticsearch的站内搜索设计,搜索,gitlab,elasticsearch,搜索引擎

gitlab的insert, update, delete操作会推送到redis的zset中(作为queue使用)。

redis的zset是有序集合,可以有效防止减少重复消息,提高ES的写效率。

sidekiq(ruby领域的异步框架)周期采用bulk api批量写ES,提高ES的写性能和保障ES集群的整体性能。(参考:Keeping Elasticsearch in Sync | Elastic Blog)

搜索分析

子对象的搜索
{
    "from": 0,
    "size": 20,
    "timeout": "30s",
    "query": {
        "bool": {
            "must": [
                {
                    "simple_query_string": {
                        "query": "领域驱动",
                        "fields": [
                            "title^2.0",
                            "description^1.0"
                        ],
                        "flags": -1,
                        "default_operator": "and",
                        "lenient": true,
                        "analyze_wildcard": false,
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_prefix_length": 0,
                        "fuzzy_max_expansions": 50,
                        "fuzzy_transpositions": true,
                        "boost": 1.0,
                        "_name": "milestone:match:search_terms"
                    }
                }
            ],
            "filter": [
                {
                    "term": {
                        "type": {
                            "value": "milestone",
                            "boost": 1.0,
                            "_name": "doc:is_a:milestone"
                        }
                    }
                },
                {
                    "has_parent": {
                        "query": {
                            "bool": {
                                "should": [
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 0,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:any"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "issues_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:issues:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0
                                        }
                                    },
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 0,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:any"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "merge_requests_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:merge_requests:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0
                                        }
                                    },
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 10,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:visibility:10"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "issues_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:visibility:10:issues:access_level:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0,
                                            "_name": "milestone:related:project:visibility:10:issues:access_level"
                                        }
                                    },
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 10,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:visibility:10"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "merge_requests_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:visibility:10:merge_requests:access_level:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0,
                                            "_name": "milestone:related:project:visibility:10:merge_requests:access_level"
                                        }
                                    },
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 20,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:visibility:20"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "issues_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:visibility:20:issues:access_level:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0,
                                            "_name": "milestone:related:project:visibility:20:issues:access_level"
                                        }
                                    },
                                    {
                                        "bool": {
                                            "filter": [
                                                {
                                                    "term": {
                                                        "visibility_level": {
                                                            "value": 20,
                                                            "boost": 1.0,
                                                            "_name": "milestone:related:project:visibility:20"
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "merge_requests_access_level": [
                                                            20,
                                                            10
                                                        ],
                                                        "boost": 1.0,
                                                        "_name": "milestone:related:project:visibility:20:merge_requests:access_level:enabled_or_private"
                                                    }
                                                }
                                            ],
                                            "adjust_pure_negative": true,
                                            "boost": 1.0,
                                            "_name": "milestone:related:project:visibility:20:merge_requests:access_level"
                                        }
                                    }
                                ],
                                "adjust_pure_negative": true,
                                "boost": 1.0
                            }
                        },
                        "parent_type": "project",
                        "score": false,
                        "ignore_unmapped": false,
                        "boost": 1.0,
                        "_name": "milestone:related:project"
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    },
    "highlight": {
        "pre_tags": [
            "gitlabelasticsearch→"
        ],
        "post_tags": [
            "←gitlabelasticsearch"
        ],
        "number_of_fragments": 0,
        "fields": {
            "title": {},
            "description": {}
        }
    }
}



搜索分为2部分:

  1. 搜索关键字逻辑(采用simple_query_string,AND逻辑,开启模糊查询)
  2. 过滤逻辑(类型为子对象类型(如milestone),且project(父对象)在权限范围内)
子对象issue搜索示例
        "_source" : {
          "id" : 2,
          "iid" : 1,
          "title" : "搜索支持相似问",
          "description" : "打开FAQ搜索时,需要加入相似问字段的搜索",
          "created_at" : "2023-04-14T08:28:38.119Z",
          "updated_at" : "2023-04-14T08:28:38.119Z",
          "state" : "opened",
          "project_id" : 3,
          "author_id" : 3,
          "confidential" : false,
          "schema_version" : 2302,
          "assignee_id" : [
            3
          ],
          "hidden" : false,
          "visibility_level" : 0,
          "issues_access_level" : 10,
          "upvotes" : 1,
          "namespace_ancestry_ids" : "8-",
          "label_ids" : [
            "2"
          ],
          "type" : "issue"
        }

凡是涉及其他对象的字段,全部使用引用对象的id (例如,标签label_ids,存储的是标签的id,而不是具体的值)

但这样有一个问题,导致了无法做到标签搜索。(例如,给一个issue添加知识库的标签,搜索 知识库,并不能搜到 这个issue)

iid为issue的子对象。

issue本身的iid为1,添加其他子对象,iid依次递增的分配。(例如,新建task,task的iid为2,task的type为work_item)

并不是所有的issue属性都会同步到es中,例如,issue的评论,虽然包含文字,但是没有同步到es索引中。(评论的重要性低,加入搜索范围,可能会加大搜索结果噪音)

visibility_level 代表project的可见性

issues_access_level 代表issue的可访问性

权限过滤逻辑为(或的关系):

  • 查询有权限的project 且 issue的权限为可被访问
  • 项目为登录用户可见 且 issue可被所有人访问
  • 项目可被所有人可见 且 issue可被所有人访问

(作者注:可以优化为  issue可被所有人访问(20) 或 (issue需要有权限才能访问(10) 且 具有该项目的权限)

权限控制设计

权限分为

  • private(0)
  • internal(10)
  • public(20)

后面的数字代表ES里的对应权限的值(不存字符串,而是存数字,且数字采用了10的间隔,猜测为了考虑未来在中间插入的拓展)

project和它子对象都有自己独立的权限值。



project在gitlab-production中的结构为:

       
 "_source" : {
          "id" : 3,
          "name" : "eim-search",
          "path" : "eim-search",
          "description" : null,
          "namespace_id" : 8,
          "created_at" : "2023-04-14T02:53:42.747Z",
          "updated_at" : "2023-04-14T02:53:44.471Z",
          "archived" : false,
          "visibility_level" : 0,
          "last_activity_at" : "2023-04-14T02:53:42.747Z",
          "name_with_namespace" : "platform / eim-search",
          "path_with_namespace" : "platform/eim-search",
          "join_field" : "project",
          "type" : "project",
          "schema_version" : 2301,
          "traversal_ids" : "8-p3-",
          "issues_access_level" : 20,
          "merge_requests_access_level" : 20,
          "snippets_access_level" : 20,
          "wiki_access_level" : 20,
          "repository_access_level" : 20
        }

namespace_id 就是group的id(namespace=group)

traversal_ids 通过namespace_id和project_id 拼接而成

join_field 和 type 虽然都被赋予了相同的值,但是作用不一样。

join_field 是用于has parent query,在这个query里,充当parent_type的值

type只是本身的对象属性

user在gitlab-production-users的结构为:

        "_source" : {
          "id" : 3,
          "username" : "yanyongwen",
          "email" : "yyw794@126.com",
          "public_email" : null,
          "name" : "yyw794",
          "created_at" : "2023-04-14T02:34:14.040Z",
          "updated_at" : "2023-04-14T02:53:04.822Z",
          "admin" : false,
          "state" : "active",
          "organization" : "",
          "timezone" : null,
          "external" : false,
          "in_forbidden_state" : false,
          "status" : null,
          "status_emoji" : null,
          "busy" : false,
          "namespace_ancestry_ids" : [
            "2-p2-",
            "8-"
          ],
          "schema_version" : 2210,
          "type" : "user"
        }

用户的权限通过namespace_ancestry_ids进行存储

通过namespace-project的id拼接方便进行权限控制。

基于父子数据建模的权限控制设计

父子数据建模使用了ES的Has Parent Query

为什么Gitlab不使用ES官方推荐的大宽表数据建模?

gitlab的搜索对象存在父子关系,且子对象也需要被独立搜索出来,因此,ES内部的子对象是独立对象存储的。

每个project下面有多种类型的子对象,每种子对象都可能数量众多。

缺点:写入操作变得繁琐

  • 如果采用大宽表设计,当project的权限改变时,该project的全部子对象的project权限属性都要同步更新,涉及面很广。
  • project每新增一个子对象时,需要查询project的属性后,再填入子对象中。

采用父子关系的数据建模

写入过程,需要额外增加route属性,保证父子对象(同一个project)都在同一个shard中。

由于是project级别的路由,因此_route值为"project-${project_id}"

父子关系的建模在什么数据量下的性能变得不可接受?(gitlab的搜索不是一个高频操作,每个project下子对象总数也不会太高)

父子关系的数据建模适合:

  • 整体的父对象不多,但是父对象内部的子对象较多的场景
  • 搜索性能要求不高

索引版本管理

具有schema_version字段,Format is YYMM (如2303),当schema改变时,这个值需要变更。

ES ID设计

ES ID设计的核心是唯一性。(_Id 字段)

ES的文档_id

两种设计思路:

  • ProjectID_项目内唯一识别字符  (用于对象的唯一识别是项目内唯一的)
  • 对象类型_对象ID (用于对象的ID是全局唯一的)

ProjectID_项目内唯一识别字符

blob的ID设计为:

${project_id}_${blob_path}

(wiki_blob采用和blob一样的设计)

commit:

${project_id_${sha}

对象类型_对象ID

project的ID设计为:

project_${project_id}

milestone的ID设计为:

milestone_${milestone_id}

snippet的ID设计为:

snippet_${snippet_id}

source内部的ID仍然使用对象自己的业务ID(_source内部的id和ES的_id的不一样,如何做到的?TODO:)

子对象ID

通过例如repository的id名为rid (有较好的可读性)

rid: repository id

一个project下面会有2个repository

1个为代码仓库,id和project一致

1个为wiki仓库,id为wiki_${project_id}

oid: blob id / wiki_blob id

附录

通过查看ES的日志,来获取gitlab实际的搜索query。

基于admin的query json

https://download.csdn.net/download/yyw794/88646878

gitlab在es中碰过的坑:

Lessons from our journey to enable global code search with Elasticsearch on GitLab.com

Update: The challenge of enabling Elasticsearch on GitLab.com

Update: Elasticsearch lessons learnt for Advanced Global Search 2020-04-28

减少索引体积

由于ES的delete是软删除,gitlab最初采用forcemerge来强制硬删除(merge segment的过程会最终硬删除文档),但是forcemerge是一个阻塞操作,会严重影响ES的整体性能,因此只能放弃forcemerge。

不同领域对象在一个大索引 还是不同领域对象在不同索引的问题。文章来源地址https://www.toymoban.com/news/detail-775067.html

  • 不存在空间占用区别。ES6引入了sparse field功能,使得1个大索引的稀疏字段并不会浪费额外的空间
  • 独立索引具有重建索引时的速度优势 (Elasticsearch: return to using a separate index per document type (#3217) · Issues · GitLab.org / GitLab · GitLab)

到了这里,关于【独家深度】Gitlab基于Elasticsearch的站内搜索设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 什么是站内搜索引擎?如何在网站中加入站内搜索功能?

    在当今数字时代,用户体验对于网站的成功起着至关重要的作用。提升用户体验和改善整体网站性能的一种方法是引入站内搜索引擎。站内搜索引擎是一种强大的工具,它的功能类似于Google或Bing等流行搜索引擎,但它专注于实施自己网站上的内容。用户可以在网站内搜索特定

    2024年02月03日
    浏览(76)
  • 站内搜索引擎

    1.什么是搜索引擎? 如图所示: 我用的是谷歌浏览器,但是我的搜索引擎可以跟换 。切换到bing主页 在搜索框中我们输入一段话,跳到一个带有搜索结果的页面如下: 搜索引擎的核心功能:查找用户输入的词/一句话 相关联的网页。  搜索结果页一条记录包含的信息如下:

    2023年04月11日
    浏览(45)
  • 李宏毅transformer讲解;B站内测“搜索AI助手”功能

    🦉 AI新闻 🚀 B站内测“搜索AI助手”功能 摘要 :据反馈,B站正在内测“搜索 AI 助手”功能。用户在搜索框内输入问句或在搜索词中添加“?”即可体验此新功能。截图显示,该功能会为用户的搜索提供一个生成的答案,并附上相关参考视频。B站董事长兼首席执行官陈睿曾表

    2024年02月12日
    浏览(40)
  • 只使用JS怎么给静态页面网站添加站内全局搜索功能?

    💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】 🤟 基于Web端打造的:👉轻量化工具创作平台 💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 静态页面通常由HTML、CSS 和 JavaScript 等静态文件组成,这些文件在服务器上不会动态生成或修改,所以加

    2024年02月05日
    浏览(43)
  • 如何进行SEO站内优化,让你的网站更易被搜索引擎收录

    我们了解了 SEO 的流程,知道了哪些元素对 SEO 的效果会产生关键影响,接下来,我们就该正式开始动手, 打造一个让搜索引擎“爱不释手”的网站 。 为了方便理解与记忆,我们将网站划分为几个模块,告诉你优化网站应该从什么地方入手。 如果你了解什么是代码标签,请

    2024年02月06日
    浏览(122)
  • Elasticsearch权威指南:深度解析搜索技术核心概念、原理及实践

    作者:禅与计算机程序设计艺术 2010年,当时仅仅30岁的Elasticsearch创始人黄文坚就率先发布了开源分布式搜索引擎Elasticsearch。从此, Elasticsearch 名扬天下,成为了当前搜索领域的翘楚。随着 Elasticsearch 的快速崛起,越来越多的人开始关注并应用 Elasticsearch 来进行搜索服务。

    2024年02月10日
    浏览(65)
  • [C++项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍...

    Boost 库是 C++ 中一个非常重要的开源库. 它实现了许多 C++ 标准库中没有涉及的特性和功能, 一度成为了 C++ 标准库的拓展库. C++ 新标准的内容, 很大一部分脱胎于 Boost 库中. Boost 库的高质量代码 以及 提供了更多实用方便的 C++ 组件, 使得 Boost 库在 C++ 开发中会被高频使用 为方便

    2024年02月14日
    浏览(46)
  • [C++项目] Boost文档 站内搜索引擎(4): 搜索的相关接口的实现、线程安全的单例index接口、cppjieba分词库的使用、综合调试...

    有关 Boost 文档搜索引擎的项目的前三篇文章, 已经分别介绍分析了: 项目背景: 🫦[C++项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍… 文档解析、处理模块 parser 的实现: 🫦[C++项目] Boost文档 站内搜索引擎(2): 文档文本解析模块parser的实现、如何对

    2024年02月14日
    浏览(54)
  • [C++项目] Boost文档 站内搜索引擎(5): cpphttplib实现网络服务、html页面实现、服务器部署...

    在前四篇文章中, 我们实现了从文档文件的清理 到 搜索的所有内容: 项目背景: 🫦[C++项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍… 文档解析、处理模块 parser 的实现: 🫦[C++项目] Boost文档 站内搜索引擎(2): 文档文本解析模块parser的实现、如何对

    2024年02月13日
    浏览(47)
  • Elasticsearch 使用scroll滚动技术实现大数据量搜索、深度分页问题 和 search

    基于scroll滚动技术实现大数据量搜索 如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scroll滚动查询,一批一批的查,直到所有数据都查询完为止。 scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜

    2024年02月14日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包