Commit 4503d8bfb503652d7e57d83390c8789508ac1bc7

Authored by tangwang
1 parent 3a33657d

更新文档《索引方案.md》

config/query_rewrite.dict
1 -芭比 vendor.keyword:芭比 OR title:芭比娃娃  
2 -玩具 category.keyword:玩具 OR title:玩具  
3 -消防 category.keyword:消防 OR title:消防 1 +玩具 category.keyword:玩具 OR default:玩具
  2 +消防 category.keyword:消防 OR default:消防
4 3
docs/image-dress1.png 0 → 100644

125 KB

docs/image-dress2.png 0 → 100644

27.3 KB

docs/image-shoes1.png 0 → 100644

85.2 KB

docs/索引spu-sku层级结构设计.md 0 → 100644
@@ -0,0 +1,388 @@ @@ -0,0 +1,388 @@
  1 +
  2 +
  3 +公司文档地址:https://www.kdocs.cn/l/cav0b2rdk42n?from=docs&startTime=1763618428738&createDirect=true&newFile=true
  4 +飞书地址:https://xp0y6kmku6.feishu.cn/wiki/ToYTwpjkViqPkZkcOz7cTZopnyc
  5 +
  6 +SPU-SKU索引方案选型
  7 +1. SKU为单位 + Collapse + inner_hits
  8 +1. 优点:
  9 +1. sku评分精准 (SKU 级别评分。如果sku影响相关性较多)
  10 +2. 可扩展性:如果以后需要返回sku级别结果,索引改动小。
  11 +3. 索引更新灵活,单个sku字段变化只会引起单个sku的更新。
  12 +2. 缺点:
  13 +1. inner_hits性能很差
  14 +2. spu级别的查询字段冗余,冗余倍数跟spu下面平均sku数量线性相关
  15 +3. 方案介绍
  16 +1. 查询性能:
  17 + a. 主查询执行(召回结果)(比如5000sku) → collapse折叠(按spu_id分组、每组选top文档,比如得到1000sku) → 排序、按spu排序截取分页内结果(比如100) → 执行inner_hits (获取 spu内sku,相当于执行 page_size 次子查询)
  18 + b. collapse阶段的耗时影响因素:
  19 + i. 召回数量 、 基数(Cardinality)(去重后的spu数量,高基数字段collapse性能差)。
  20 + c. Inter hits(主要开销来源):
  21 + i. 取决于分页size。(相当于page_size次子查询,"max_concurrent_group_searches"限制并发组查询)
  22 + ii. 我们在宜采的实践,分页size=100的情况下,Inter hits开销非常大(Inter hits size = 4,只取id 不取任何sku字段、按原始打分排序)
  23 + d. Inner hits 为什么性能差?
  24 + i. 不对分桶内结果进行一次排序就行了吗?一个for循环的事情,为什么要用max_concurrent_group_searches线程去并发?
  25 + ii. Collapse 阶段只保留每个分组的最佳文档,其他文档被丢弃。
  26 + iii. Collapse 阶段:每个分片独立执行collapse、最后协调节点做全局collapse。
  27 + iv. Inner_hits 阶段:每个spu下的sku可能在多个分片上,因此需要跨分片查询。
  28 + v. ES为什么要在collapse阶段丢弃其他文档:
  29 + 1. 我的理解是,ES可能认为多数时候不需要inner_hits。Lazy Loading。对需要inner_hits的场景很不友好。
  30 + e. 即使collapse阶段只保留了最佳文档,为什么inner_hits阶段 为什么做page_size 100次查询、而不是做一次查询:spu_id in [1, 2, 3, ... 100个 ] AND (原始查询条件) 。 我们宜采的搜索就是这么做的。
  31 +阶段1:主查询 + Collapse
  32 +├─ 每个分片执行查询
  33 +├─ 每个分片执行 collapse(只保留最高分文档)
  34 +├─ 协调节点合并分片结果
  35 +└─ 输出:1000 个 SPU(每个只有 1 个 SKU)
  36 +
  37 +阶段2:Inner_hits(只对需要返回的分组执行)
  38 +├─ 输入:100 个 SPU(size=100)
  39 +├─ 对每个 SPU 执行查询:
  40 +│ ├─ 构建查询:spu_id=123 AND (原始查询条件)
  41 +│ ├─ 发送到所有分片
  42 +│ ├─ 等待分片响应
  43 +│ ├─ 合并分片结果
  44 +│ └─ 排序和截取(size=3)
  45 +└─ 输出:100 个 SPU,每个有 3 个 SKU,按要求排序和填充2. 索引:
  46 + a. 缺点:spu级别的查询字段冗余。比如1个spu下面有2*5个款式,那么 spu_title brielf description seo_keywords cate tags vendor 这些 spu 字段 将被重建10次索引。
  47 + b. 优点:索引更新灵活,单个sku字段变化只会引起单个sku的更新。
  48 +4. 参考资料
  49 +1. [Collapse search results](https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html) - collapse 在每个分片上独立执行,每个分片为每个折叠键选择代表文档,协调节点merge最佳文档
  50 +2. [Retrieve inner hits](https://www.elastic.co/guide/en/elasticsearch/reference/current/inner-hits.html) 每个折叠结果会触发额外的查询。
  51 +5. 适用场景:
  52 +sku级别的字段对打分影响较大,搜索需要重点关注 sku级别的title、属性的匹配情况。
  53 +需要关注spu内部sku排序。
  54 +6. 跟我们的适配情况:
  55 +1. 我们的 款式 多数情况并不影响搜索结果。
  56 + a. 相关性影响最重的 基本都在spu :spu_title brielf description seo_keywords cate tags vendor
  57 + b. 款式:需要参与(甚至可以不参与)排序,但是对相关性影响很小
  58 +2. 款式
  59 + a. 笛卡尔积,variants多,冗余情况较严重
  60 +3. inner_hits的includes字段:我们可以只要ID,也就是只返回spu级别字段。
  61 + a. sku级别字段点击详情页再触发。
  62 + b. 如果需要sku级别字段,也可以将其作为spu的属性字段进行返回,而不用从inner_hits的includes获取。
  63 +4. 我们的 inner_hits 可能需要更多的source字段
  64 +2. SKU为单位 + Collapse 获得spu级别排序(不执行inner_hits)
  65 +没有inner_hits的性能问题,但是仍然有 spu级别检索字段 冗余的问题。
  66 +3. spu为单位。SKU字段展开作为SPU属性
  67 +1. 索引方案
  68 +sku的title作为spu 的sku_titles 属性。
  69 +款式字段同样展开,作为 sku 的 list 属性。
  70 +除了title, brielf description seo相关 cate tags vendor所有影响相关性的字段都在spu。 sku只有一个title。所以,可以以spu为单位,sku的title作为spu的一个字段,以list形式灌入,假设一个spu有三个sku,那么这个sku_titles字段有三个值,打分的时候按max取得打分。
  71 +# 写入 spu 级别索引
  72 +def build_product_document(product, variants):
  73 + return {
  74 + "product_id": str(product.id),
  75 + "title": product.title,
  76 +
  77 + # Variant搜索字段(展开)
  78 + "variant_titles": [v.title for v in variants],
  79 + "variant_attributes": self._extract_all_attributes(variants),
  80 +
  81 + # Variant详细信息(用于返回)
  82 + "variants": [
  83 + {
  84 + "id": str(v.id),
  85 + "title": v.title,
  86 + "price": float(v.price),
  87 + "options": v.options
  88 + }
  89 + for v in variants
  90 + ],
  91 +
  92 + # titles参与相关性计算
  93 + "titles": list(set([v.title for v in variants if v.title])),
  94 +
  95 + # 属性字段(扁平化,用于过滤)
  96 + "option1": list(set([v.option1 for v in variants if v.option1])),
  97 + "sizes": list(set([v.option2 for v in variants if v.option2])),
  98 +
  99 + "min_price": min(v.price for v in variants),
  100 + "max_price": max(v.price for v in variants)
  101 + }2. 查询方案
  102 +对数组字段使用 dis_max,只取最高分,避免累加。
  103 +3. 问题
  104 +不好得到每个子title的得分以决定sku的排序。方案:
  105 +1. 打开explain,会影响性能,不推荐。(只需要记下来sku_titles 中每一项的打分,但是打开explain会记录太多的东西)
  106 +2. 修改ES,把 sku_titles 数组中 每个title的匹配得分记下来。
  107 +3. 对返回的结果的子sku根据title和属性的匹配情况再做一次匹配,以决定sku的排序。
  108 +4. sku 作为nested
  109 +如果需要查询nested字段,会需要nested join,本质是是父子文档的join。
  110 +性能影响因子:- 子文档数量:线性增长,每个SKU增加约 5-15ms 查询时间- 嵌套深度:指数增长,多级嵌套性能急剧下降- 查询复杂度:nested bool查询比普通查询慢 3-10倍
  111 +原因:
  112 +1. 内部 Join 操作:ES 为每个 nested 对象创建隐藏文档,查询时需要 join
  113 +2. 文档膨胀:1个SPU文档实际存储为 1+N 个文档(N=SKU数量)
  114 +3. 内存开销:nested 查询需要加载所有匹配的父子文档到内存
  115 +适用场景:
  116 +• 查询模式简单(主要是过滤,少用复杂评分)
  117 +5. Spu sku 独立索引
  118 +spu-catalog-*:用于品牌、类目、商品介绍等宏观搜索。包括列表页搜索。
  119 +sku-catalog-*:用于具体规格搜索、下单。涉及到价格、库存筛选的查询。
  120 +
  121 +两者各有各的字段:
  122 +SPU索引:
  123 +├─ 数据:品牌、类目、描述、SEO信息
  124 +├─ 更新频率:低(几天一次)
  125 +├─ 查询场景:列表页、品牌搜索、类目浏览
  126 +└─ 优化方向:文本搜索、相关性排序
  127 +
  128 +SKU索引:
  129 +├─ 数据:规格、价格、库存
  130 +├─ 更新频率:高(实时更新)
  131 +├─ 查询场景:详情页、规格筛选、价格排序
  132 +└─ 优化方向:精确匹配、数值范围查询
  133 +联合查询:使用 multi_search 或应用层合并
  134 +
  135 +这种场景的效果 可以等同于 sku 作为nested方案,如果 sku 的nested字段,只有数值型的用于查询,其他的的只用于返回填充。
  136 +
  137 +店匠商家SPU-SKU数据情况调研
  138 +商品(product)的多款式(variants)配置
  139 +3种属性/款式,是笛卡尔积的形式生成variants(sku)
  140 +服装领域的多款式
  141 +服装领域,variants(sku)基本上都是 颜色、size。
  142 +size 基本上每个商品都有:
  143 +XS S M L XL 3XL,有的还有4XL,5XL
  144 +颜色: blue/orange/purple/green...
  145 +款式:dress1 dress2 shirt1 shirt2 swimsuit1 set1 set2...
  146 +
  147 +当前的多数独立站多款式是否参与搜索
  148 +看起来搜索的索引粒度是spu单位的。 query加上sku相关信息,并不会让sku在spu内更靠前
  149 +
  150 +下面的几个案例,都是我用 spu名称 + 其中一款的款式名称 进行搜索,看召回的spu 是否会将我指定的款式前置。 可以看到不管我是否加款式限定,返回结果的每个spu下的子sku顺序都不会受影响。
  151 +
  152 +
  153 +
  154 +
  155 +
  156 +SPU-SKU索引建议方案
  157 +spu为单位。SKU字段展开作为SPU属性
  158 +其他重点字段
  159 +1. Sku title
  160 +2. category
  161 +1. 数据源
  162 +2. Mysql
  163 +在spu表中:
  164 +Field Type
  165 +category varchar(255)
  166 +category_id bigint(20)
  167 +category_google_id bigint(20)
  168 +category_level int(11)
  169 +category_path varchar(500)
  170 +3. ES索引
  171 +1. 输入数据
  172 + 设计 1,2,3级分类 三个字段,的 category (原始文本)
  173 +2. 索引方法
  174 + 设计要求:
  175 +1. 支持facet(精确过滤、keyword聚合),并且性能需要足够高。
  176 +2. 支持普通搜索模糊匹配(用户原始query可能包括分类词)。
  177 +3. 模糊匹配要考虑多语言
  178 +方案:采用方案2
  179 +4. categoryPath索引 + Prefix 查询(categoryPath.keyword: "服装/男装")(如果满足条件的key太多的则性能较差,比如 查询的是一级类目,类目树叶子节点太多时性能较差)
  180 +5. categoryPath支撑模糊查询 和 多级cate keyword索引支撑精确查询。 索引阶段冗余,查询性能高。
  181 + "category_path_zh": { // 提供模糊查询功能,辅助相关性计算
  182 + "type": "text",
  183 + "analyzer": "hanlp_index",
  184 + "search_analyzer": "hanlp_standard"
  185 + },
  186 + "category_path_en": { // 提供模糊查询功能,辅助相关性计算
  187 + "type": "text",
  188 + "analyzer": "english",
  189 + "search_analyzer": "english"
  190 + },
  191 + "category_path": { // 用于多层级的筛选、精确匹配
  192 + "type": "keyword",
  193 + "normalizer": "lowercase"
  194 + },
  195 + "category_id": {
  196 + "type": "keyword"
  197 + },
  198 + "category_name": {
  199 + "type": "keyword"
  200 + },
  201 + "category_level": {
  202 + "type": "integer"
  203 + },
  204 + "category1_name": { // 不同层级下 可能有同名的情况,因此提供一二三级分开的查询方式
  205 + "type": "keyword"
  206 + },
  207 + "category2_name": {
  208 + "type": "keyword"
  209 + },
  210 + "category3_name": {
  211 + "type": "keyword"
  212 + },
  213 +3. tags
  214 +
  215 +1. 数据源
  216 +多值
  217 +标签
  218 +
  219 +最多输入250个标签,每个不得超过500字符,多个标签请用「英文逗号」隔开
  220 +
  221 +新品,热卖,爆款
  222 +
  223 +耳机,头戴式,爆款
  224 +
  225 +分割后 list形式灌入
  226 +2. Mysql
  227 +3. ES索引
  228 +1. 输入数据
  229 +2. 索引方法
  230 +4. 供应商
  231 +1. 数据源
  232 +2. Mysql
  233 +3. ES索引
  234 +1. 输入数据
  235 +2. 索引方法
  236 +5. 款式/选项值(options)
  237 +1. 数据源
  238 +以下区域字段,商品属性为M(商品主体)的行需填写款式名称,商品属性为P(子款式)的行需填写款式值信息,商品属性为S(单一款式商品)的行无需填写
  239 +
  240 +款式1
  241 +款式2
  242 +款式3
  243 +
  244 +最多255字符
  245 +最多255字符
  246 +最多255字符
  247 +
  248 +SIZE
  249 +COLOR
  250 +
  251 +
  252 +S
  253 +red
  254 +
  255 +
  256 +S
  257 +black
  258 +
  259 +
  260 +S
  261 +army
  262 +
  263 +
  264 +L
  265 +red
  266 +
  267 +
  268 +L
  269 +black
  270 +
  271 +
  272 +L
  273 +army
  274 +
  275 +
  276 +XL
  277 +red
  278 +
  279 +
  280 +XL
  281 +black
  282 +
  283 +
  284 +XL
  285 +army
  286 +
  287 +
  288 +2. Mysql
  289 +1. API 在 SPU 的维度直接返回3个属性定义,存储在 shoplazza_product_option 中:
  290 +2. API在 SKU的维度直接返回3个属性值,存储在 shoplazza_product_sku 表的 option 相关的字段中:
  291 +3. ES索引
  292 +1. 输入数据
  293 +2. 索引方法
  294 + 1)铺平展开,只支持三个,支持查询。缺点是,查询逻辑跟租户的属性维度绑定,不灵活
  295 + attr1
  296 + attr2
  297 + attr3
  298 + option1
  299 + option2
  300 + option3
  301 + 2)nested,支持超过3个属性(动态)。支持查询
  302 + "specifications": {
  303 + "type": "nested",
  304 + "properties": {
  305 + "name": { "type": "keyword" }, // "颜色", "容量"
  306 + "value": { "type": "keyword" } // "白色", "256GB"
  307 + }
  308 + },
  309 + 3)nested,支持超过3个属性(动态)。只用作返回,不能查询。节省索引空间
  310 + "specifications": {
  311 + "type": "nested",
  312 + "properties": {
  313 + "name": { "type": "keyword","index": false },
  314 + "value": { "type": "keyword","index": false }
  315 + }
  316 + },
  317 +
  318 + TODO 考虑使用方案(3)。
  319 +6. SEO相关字段
  320 +1. 数据源
  321 +SEO标题
  322 +SEO描述
  323 +SEO URL Handle
  324 +SEO URL 重定向
  325 +SEO关键词
  326 +
  327 +最多5000字符
  328 +最多5000字符
  329 +最多支持输入255字符
  330 + (SEO URL handle只对SEO URL的「URL参数」部分进行更改,即“products/”后的内容,如:products/「URL参数」
  331 + )
  332 +创建URL重定向,访问修改前链接可跳转到修改后的新链接页面
  333 +「Y」:TRUE
  334 +「N」:FALSE
  335 +多个关键词请用「英文逗号」隔开
  336 +
  337 +
  338 +2. Mysql
  339 +3. ES索引
  340 +1. 输入数据
  341 +2. 索引方法
  342 +7. 供应商
  343 +1. 数据源
  344 +供应商名称
  345 +供应商URL
  346 +
  347 +最多20字符
  348 +请输入供应商URL
  349 +
  350 +Amazon
  351 +https://www.amazon.com/Legendary-Whitetails-Buck-Flannels-Large/dp/B01KTUMBOI/ref=sr_1_1?s=fashion-mens-intl-ship&ie=UTF8&qid=1543038722&sr=1-1
  352 +
  353 +2. Mysql
  354 +
  355 +3. ES索引
  356 +1. 输入数据
  357 +2. 索引方法
  358 +8. 其他
  359 +1. 数据源
  360 +2. Mysql
  361 +3. ES索引
  362 +1. 输入数据
  363 +2. 索引方法
  364 +索引整体配置
  365 +常用查询域(Query Domains)
  366 +1. default(默认索引): 搜索所有文本字段
  367 + a. 包含字段:title, sku_titles(TODO), brief, description, seo_title(TODO待确认), seo_keywords, vendor, category_path, tags, category (带具体的语言后缀)
  368 +2. title(标题索引): 仅搜索标题相关字段
  369 + a. 包含字段:title, sku_titles, seo_title (带具体的语言后缀)
  370 +3. vendor(品牌索引): 仅搜索品牌字段
  371 + a. 包含字段:vendor (带具体的语言后缀)
  372 +4. category_path(类目索引): 仅搜索类目字段
  373 + a. 包含字段:category_path(带具体的语言后缀)
  374 +5. tags(标签索引): 搜索标签和SEO关键词
  375 + a. 包含字段:tags, seo_keywords
  376 +提权字段
  377 +function_score
  378 +具体提权字段、权重待定(配置化)
  379 +"function_score": {
  380 + "functions": [
  381 + { "field_value_factor": { "field": "sales_count", "factor": 0.001, "modifier": "log1p" } },
  382 + { "gauss": { "listed_at": { "scale": "30d" } } }
  383 + ],
  384 + "boost_mode": "multiply"
  385 +}facet
  386 +分类
  387 +tags
  388 +
docs/索引字段说明.md
@@ -171,28 +171,31 @@ category_path varchar(500) @@ -171,28 +171,31 @@ category_path varchar(500)
171 ### 属性 171 ### 属性
172 1. 组织ES输入数据的时候,需要为sku拼接spu的 option1 option2 option3,作为属性名称(比如“颜色”),sku的 option1 option2 option3 作为属性值(比如“白色”) 172 1. 组织ES输入数据的时候,需要为sku拼接spu的 option1 option2 option3,作为属性名称(比如“颜色”),sku的 option1 option2 option3 作为属性值(比如“白色”)
173 2. 有以下方案: TODO 可以选择其中一种,或者2用于填充3用于搜索: 173 2. 有以下方案: TODO 可以选择其中一种,或者2用于填充3用于搜索:
174 -1)铺平展开,只支持三个 174 +1)铺平展开,只支持三个,支持查询。缺点是,查询逻辑跟租户的属性维度绑定,不灵活
175 attr1 175 attr1
176 attr2 176 attr2
177 attr3 177 attr3
178 option1 178 option1
179 option2 179 option2
180 option3 180 option3
181 -2)nested,支持多个,动态。 查询性能低 181 +2)nested,支持超过3个属性(动态)。支持查询
182 "specifications": { 182 "specifications": {
183 "type": "nested", 183 "type": "nested",
184 "properties": { 184 "properties": {
185 - "name": { "type": "keyword" }, // "颜色", "容量"  
186 - "value": { "type": "keyword" } // "白色", "256GB" 185 + "name": { "type": "keyword" }, // "颜色", "容量"
  186 + "value": { "type": "keyword" } // "白色", "256GB"
187 } 187 }
188 }, 188 },
189 -3)平铺展开。写入时从 specifications 提取并填充这些字段,查询性能高。  
190 -"properties": {  
191 - "color": { "type": "keyword" },  
192 - "capacity": { "type": "keyword" },  
193 - "network": { "type": "keyword" },  
194 - "edition": { "type": "keyword" }  
195 -} 189 +3)nested,支持超过3个属性(动态)。只用作返回,不能查询。节省索引空间
  190 +"specifications": {
  191 + "type": "nested",
  192 + "properties": {
  193 + "name": { "type": "keyword","index": false },
  194 + "value": { "type": "keyword","index": false }
  195 + }
  196 +},
  197 +
  198 +考虑使用方案(3)。
196 199
197 ### status 200 ### status
198 1. 商品下架等状态 201 1. 商品下架等状态
@@ -218,30 +221,7 @@ SEO关键词 @@ -218,30 +221,7 @@ SEO关键词
218 221
219 ## SPU 与 SKU 的协同设计 222 ## SPU 与 SKU 的协同设计
220 223
221 -以下方案:  
222 -1. sku为索引单位。使用 collapse 按 spu_id 折叠  
223 -需要考虑大量的字段冗余  
224 -  
225 -2. spu为单位。 sku的title作为 spu 的sku_titles 属性。  
226 - 除了title, brielf description seo相关 cate tags vendor所有影响相关性的字段都在spu。 sku只有一个title。所以,可以以spu为单位,sku的title作为spu的一个字段,以list形式灌入,假设一个spu有三个sku,那么这个sku_titles字段有三个值,打分的时候按max取得打分,并且我们可以得到这三个sku的title匹配的得分,因此好决定sku的排序。  
227 -  
228 -3. sku 作为nested  
229 -  
230 -  
231 -  
232 -参考 [](https://blog.csdn.net/csdn_tom_168/article/details/150432666)  
233 -方案一:独立索引  
234 -spu-catalog-*:用于品牌、类目、商品介绍等宏观搜索。  
235 -sku-catalog-*:用于具体规格搜索、下单。  
236 -优势:职责分离,查询更高效。  
237 -  
238 -方案二:联合查询  
239 -GET /spu-read,sku-read/_search  
240 -适用于“模糊搜索 → 跳转详情页”的场景。  
241 -  
242 -方案三:父子文档(不推荐)  
243 -join 类型维护 SPU-SKU 关系。  
244 -性能差,维护复杂,不适用于高并发搜索场景。 224 +参考《索引spu-sku层级结构设计.md》
245 225
246 226
247 ## rank - 相关性 227 ## rank - 相关性