Blame view

suggestion/ARCHITECTURE_V2.md 7.62 KB
ff9efda0   tangwang   suggest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
  # Suggestion 架构方案 V2(仅 Suggest,去除结果直达)
  
  ## 0. 结论
  
  本方案将 Suggest 设计为**独立高性能检索系统**,只返回建议词,不再返回商品卡片,也不做历史兼容。
  
  - 只保留 `/search/suggestions` 的词级自动补全能力
  - 完全移除 `with_results/result_size/products[]` 链路
  - 多语言优先,支持高并发、低延迟、可持续演进
  
  ---
  
  ## 1. 当前实现的关键问题(基于现有代码审视)
  
  1. 在线链路曾包含“suggest -> 二次商品查询”,属于典型 N+1 放大,QPS 上升后延迟和 ES 负载都不稳定。
  2. `builder.py` 全量构建使用“大量 in-memory 聚合 + fetchall”,大租户下内存风险高。
  3. 查询参数上限过大(原 `size<=200`),不符合自动补全接口性能边界。
  4. 文档与实现长期混合(README 仍包含结果直达),导致认知不一致。
  5. 多语言归一化仍偏基础(仅 lower/空白折叠),对 Unicode、变音符、跨语系兼容不够。
  
  ---
  
  ## 2. 目标与 SLO
  
  ### 2.1 业务目标
  
  - 输入时实时返回高相关建议词(query suggestion)
  - 多语言稳定(至少覆盖租户配置 `index_languages`
  - 支持词级排序和运营治理(黑白名单、降噪、降权)
  
  ### 2.2 性能目标(建议)
  
  - P50 < 10ms,P95 < 25ms,P99 < 50ms(ES 查询耗时,不含网关)
  - 单集群支持高并发(千级 QPS 可横向扩展)
  - 数据新鲜度:增量 5-15 分钟可见
  
  ---
  
  ## 3. 总体架构
  
  ## 3.1 在线路径(单跳)
  
  Client -> API `/search/suggestions` -> ES `search_suggestions_v2` -> 返回 suggestions
  
  原则:
  
  - **单次 ES 查询完成主路径**(可选双召回融合,但仍在同一次 API 请求内完成)
  - 不调用 `search_products`,不返回商品结果
  - 通过 `routing=tenant_id` 避免跨分片 fan-out
  
  ## 3.2 离线路径(构建)
  
  数据源:
  
  - 商品字段:`title.{lang}`、`qanchors.{lang}`
  - 搜索日志:`shoplazza_search_log`(含 `language/request_params`
  - 行为信号(可选增强):点击、加购、下单
  
  产物:
  
  - Suggest 文档(`tenant_id + lang + text_norm` 唯一)
  - completion + prefix 检索字段
  - 排序特征(热度、近期度、质量分)
  
  发布方式:
  
  - 写入新物理索引(版本化)
  - 原子切换 alias(零停机)
  
  ---
  
  ## 4. 索引设计(ES)
  
  ## 4.1 索引组织
  
  推荐两级策略:
  
  1. 默认:环境级共享索引(降低海量租户 index 数量)
  2. 大租户:可升级为租户独享索引(隔离资源)
  
  统一通过 alias 暴露:
  
  - `search_suggestions_v2_current`
  
  ## 4.2 Mapping(核心字段)
  
  ```json
  {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "30s"
    },
    "mappings": {
      "properties": {
        "tenant_id": { "type": "keyword" },
        "lang": { "type": "keyword" },
        "text": { "type": "keyword" },
        "text_norm": { "type": "keyword" },
        "status": { "type": "byte" },
        "sources": { "type": "keyword" },
  
        "query_count_7d": { "type": "integer" },
        "query_count_30d": { "type": "integer" },
        "ctr_30d": { "type": "float" },
        "order_rate_30d": { "type": "float" },
        "rank_score": { "type": "float" },
  
        "suggest": {
          "type": "completion",
          "contexts": [
            { "name": "tenant", "type": "category" },
            { "name": "lang", "type": "category" }
          ]
        },
  
        "sat": {
          "properties": {
            "zh": { "type": "search_as_you_type", "analyzer": "index_ik" },
            "en": { "type": "search_as_you_type", "analyzer": "english" },
            "ar": { "type": "search_as_you_type", "analyzer": "arabic" }
          }
        },
  
        "updated_at": { "type": "date" }
      }
    }
  }
  ```
  
  说明:
  
  - `completion` 负责极速前缀命中(主召回)
  - `search_as_you_type` 负责多词前缀和召回兜底
  - `contexts` 强制租户与语言隔离
  
  ---
  
  ## 5. 多语言策略
  
  1. 语言归属优先级:`log.language > request_params.language > 脚本识别 > tenant.primary_language`
  2. 统一归一化:NFKC、大小写折叠、空白折叠、标点清洗
  3. 分词器按语言配置:
     - 中文:IK/ANSJ(与主索引保持一致)
     - 拉丁语系:对应内置 analyzer
     - 未覆盖语种:`standard + ICU folding` 兜底
  4. 保证写入语言必须在租户 `index_languages`
  
  ---
  
  ## 6. 在线检索策略(高性能)
  
  ## 6.1 双通道召回(推荐)
  
  1. 通道 A:`completion suggester`(prefix,skip_duplicates)
  2. 通道 B:`multi_match(type=bool_prefix)` on `search_as_you_type`
  3. 融合去重:按 `text_norm` 去重,按最终分排序截断
  
  ## 6.2 查询约束
  
  - 默认 `size=10`,最大 `size=50`
  - `track_total_hits=false`
  - `_source` 仅返回必要字段(`text/lang/rank_score/sources`
  - `routing=tenant_id`
  
  ## 6.3 打分建议
  
  ```text
  final_score =
    es_score
    + a1*log1p(query_count_30d)
    + a2*log1p(query_count_7d)
    + a3*ctr_30d
    + a4*order_rate_30d
    + a5*freshness_decay
  ```
  
  ---
  
  ## 7. 构建与发布
  
  ## 7.1 构建模式
  
  - 每日全量:重建全量特征,清理脏词
  - 小时级增量:只处理新日志窗口
  
  ## 7.2 工程要求
  
  - 禁止 `fetchall` 全量入内存,改为流式读取(分页/游标)
  - ES 扫描采用 `search_after` 流式聚合
  - 批量写入采用 bulk(分块 + 重试 + 失败重放)
  
  ## 7.3 发布策略
  
  1. `search_suggestions_v2_YYYYMMDDHHmm` 写入完成
  2. 校验 count/抽样查询/核心词覆盖
  3. alias 原子切换到新索引
  4. 保留上一个版本用于快速回滚
  
  ---
  
  ## 8. API 契约(V2)
  
  请求:
  
  - `GET /search/suggestions`
  - 参数:`q`、`language`、`size`
  - Header:`X-Tenant-ID`
  
  响应:
  
  ```json
  {
    "query": "iph",
    "language": "en",
    "resolved_language": "en",
    "suggestions": [
      {
        "text": "iphone 15",
        "lang": "en",
        "score": 8.31,
        "rank_score": 6.72,
        "sources": ["query_log", "qanchor"]
      }
    ],
    "took_ms": 12
  }
  ```
  
  删除项(明确不支持):
  
  - `with_results`
  - `result_size`
  - `products[]`
  
  ---
  
  ## 9. 观测与治理
  
  核心监控:
  
  - QPS、P50/P95/P99、错误率
  - 空结果率(按语言、按租户)
  - suggestion 覆盖率(top query 是否命中)
  - 语言冲突率(log vs request_params)
  - 噪声词比例、黑名单命中率
  
  治理机制:
  
  - 黑名单:强制下线
  - 白名单:强制保留并可加权
  - 最小热度阈值:低频垃圾词过滤
  - 时间衰减:过期词自动下沉
  
  ---
  
  ## 10. 与官方最佳实践对齐(ES)
  
  本方案直接采用以下官方建议:
  
  1. `completion` 适合高性能自动补全,支持 `skip_duplicates` 与上下文过滤。
  2. `search_as_you_type + bool_prefix` 是官方推荐的 as-you-type 查询方式。
  3. `edge_ngram` 仅用于索引时分词,查询时应用普通 analyzer(`search_analyzer`)。
  4. 多语言场景使用 ICU Analysis 插件增强 Unicode 处理。
  5. 通过 `routing` 将租户请求路由到单分片,降低 fan-out。
  
  ---
  
  ## 11. 分阶段落地
  
  1. Phase 1(本次):去除结果直达,稳定 Suggest 单能力
  2. Phase 2:流式增量构建 + alias 原子发布
  3. Phase 3:行为信号排序(CTR/CVR)+ 运营治理台
  4. Phase 4:大租户独享索引自动升降级
  
  ---
  
  ## 12. Phase 2 落地命令(当前仓库)
  
  全量重建(版本化索引 + alias 发布):
  
  ```bash
  python main.py build-suggestions \
    --tenant-id 162 \
    --mode full \
    --days 365 \
    --publish-alias \
    --keep-versions 2
  ```
  
  增量更新(基于 watermark):
  
  ```bash
  python main.py build-suggestions \
    --tenant-id 162 \
    --mode incremental \
    --overlap-minutes 30
  ```
  
  一键脚本(全量 + 增量 + ES/API 验证):
  
  ```bash
  ./scripts/rebuild_suggestions.sh 162
  ```