ARCHITECTURE_V2.md 7.62 KB

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
  • 单集群支持高并发(千级 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(核心字段)

{
  "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 打分建议

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
  • 参数:qlanguagesize
  • Header:X-Tenant-ID

响应:

{
  "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 发布):

python main.py build-suggestions \
  --tenant-id 162 \
  --mode full \
  --days 365 \
  --publish-alias \
  --keep-versions 2

增量更新(基于 watermark):

python main.py build-suggestions \
  --tenant-id 162 \
  --mode incremental \
  --overlap-minutes 30

一键脚本(全量 + 增量 + ES/API 验证):

./scripts/rebuild_suggestions.sh 162