<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>搜索引擎 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 28 Aug 2021 14:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>Elasticsearch 从原理到 IPTV 媒资搜索实战：基于 ftmao-meta 项目的完整复盘</title>
      <link>https://ftmao.com/blog/posts/elasticsearch-in-action-iptv/</link>
      <pubDate>Sat, 28 Aug 2021 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/elasticsearch-in-action-iptv/</guid>
      <description>&lt;h1 id=&#34;elasticsearch-从原理到-iptv-媒资搜索实战基于-ftmao-meta-项目的完整复盘&#34;&gt;Elasticsearch 从原理到 IPTV 媒资搜索实战：基于 ftmao-meta 项目的完整复盘&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;以我们线上运行的 IPTV 媒资元数据服务 &lt;strong&gt;ftmao-meta&lt;/strong&gt; 为样本，讲清楚一个真实的 ES（实际为 AWS 托管 OpenSearch）应用是怎么设计索引、同步数据、支撑搜索和下游订阅的。所有配置、代码、索引设计均来自项目本身，踩过的坑也一并奉上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一项目背景这是一个什么系统&#34;&gt;一、项目背景：这是一个什么系统&lt;/h2&gt;
&lt;p&gt;ftmao-meta 是一个面向多地区运营的 IPTV/OTT 视频内容平台的&lt;strong&gt;媒资元数据微服务&lt;/strong&gt;，Spring Boot 3.3 + Java 21，单模块 Gradle 工程，包结构 &lt;code&gt;gtt.ftmao.meta&lt;/code&gt;，分 &lt;code&gt;api / app / bean / core&lt;/code&gt; 四层。它管理的数据包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;影视元数据&lt;/strong&gt;：电影、剧集、季、集、短视频的多语言标题、简介、海报、演职员、分级、票房；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;演职人员&lt;/strong&gt;：多语言姓名、热度值（pgcHot / ugcHot / imdbHot）、代表作；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物料&lt;/strong&gt;：剧照海报（artwork）、预告片（trailer）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C 端搜索&lt;/strong&gt;：全文检索 + 输入自动补全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些数据量巨大（千万级文档）、多语言（英语/西语/葡语，服务拉美市场）、多租户（多家运营商公司共用），而且需要被下游多个服务&lt;strong&gt;增量订阅&lt;/strong&gt;。MySQL 是事实源（Source of Truth），ES 承担所有读路径的检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术选型上的关键事实&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后端是 &lt;strong&gt;AWS 托管 OpenSearch&lt;/strong&gt;，客户端用官方 &lt;code&gt;opensearch-java 2.8.1&lt;/code&gt;（&lt;code&gt;org.opensearch.client.opensearch.OpenSearchClient&lt;/code&gt;），&lt;strong&gt;没有用 spring-data-elasticsearch，也没有 RestHighLevelClient&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;全文检索与自动补全能力封装在公司内部库 &lt;code&gt;searchengine-api/core/sync&lt;/code&gt; 里，业务代码只调用 &lt;code&gt;SearchApi&lt;/code&gt; / &lt;code&gt;SearchSync&lt;/code&gt; 门面；&lt;/li&gt;
&lt;li&gt;数据同步&lt;strong&gt;不走 MQ&lt;/strong&gt;（Kafka 依赖在启动类里被显式排除），而是&amp;quot;MySQL 任务表 + 定时轮询 + 分布式锁&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 上游[&amp;#34;上游数据来源&amp;#34;]
        BOSS[&amp;#34;运营后台 / 内容录入&amp;#34;]
        PP[&amp;#34;PP 开放平台&amp;lt;br/&amp;gt;（REST 拉取外部媒资）&amp;#34;]
    end

    subgraph META[&amp;#34;ftmao-meta 服务&amp;#34;]
        API[&amp;#34;REST API 层&amp;#34;]
        SVC[&amp;#34;业务 Service&amp;lt;br/&amp;gt;MetaService / ArtworkService ...&amp;#34;]
        JOB[&amp;#34;定时 Job&amp;lt;br/&amp;gt;PpMetaSyncJob 每 15s&amp;#34;]
        LOCK[&amp;#34;分布式锁&amp;lt;br/&amp;gt;AsyncJobExecutorManager&amp;#34;]
    end

    subgraph STORE[&amp;#34;存储层&amp;#34;]
        MYSQL[(&amp;#34;MySQL&amp;lt;br/&amp;gt;事实源 + 同步任务表&amp;#34;)]
        subgraph OS[&amp;#34;AWS OpenSearch&amp;#34;]
            DETAIL[&amp;#34;meta_detail_index&amp;lt;br/&amp;gt;媒资详情（重索引）&amp;#34;]
            PERSON[&amp;#34;meta_person_index&amp;lt;br/&amp;gt;人物详情&amp;#34;]
            ART[&amp;#34;meta_artwork_index&amp;lt;br/&amp;gt;剧照物料&amp;#34;]
            TRAILER[&amp;#34;meta_trailer_index&amp;lt;br/&amp;gt;预告片&amp;#34;]
            SEARCH[&amp;#34;search_engine_index&amp;lt;br/&amp;gt;C端搜索（轻索引）&amp;#34;]
        end
    end

    subgraph DOWN[&amp;#34;下游消费方&amp;#34;]
        PORTAL[&amp;#34;门户/推荐服务&amp;#34;]
        SUB[&amp;#34;增量订阅方&amp;lt;br/&amp;gt;（时间水位拉取变更）&amp;#34;]
    end

    BOSS --&amp;gt; SVC
    PP --&amp;gt; JOB
    SVC --&amp;gt;|&amp;#34;事务内：先写库，再直写 ES&amp;#34;| MYSQL
    SVC --&amp;gt; DETAIL
    SVC --&amp;gt; SEARCH
    JOB --&amp;gt; LOCK --&amp;gt; MYSQL
    JOB --&amp;gt;|&amp;#34;批量灌入&amp;#34;| SEARCH
    API --&amp;gt;|&amp;#34;全文搜索/补全&amp;#34;| SEARCH
    API --&amp;gt;|&amp;#34;mget 回查详情&amp;#34;| DETAIL
    API --&amp;gt; PERSON &amp;amp; ART &amp;amp; TRAILER
    DETAIL --&amp;gt;|&amp;#34;lastModifiedIndicator 水位&amp;#34;| SUB
    SEARCH --&amp;gt; PORTAL&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;二先补原理理解后面设计决策的三个支点&#34;&gt;二、先补原理：理解后面设计决策的三个支点&lt;/h2&gt;
&lt;p&gt;在进入项目细节前，用三句话回顾《Elasticsearch in Action》里最重要的原理，后面每个设计决策都会用到它们。&lt;/p&gt;
&lt;h3 id=&#34;21-倒排索引与分词&#34;&gt;2.1 倒排索引与分词&lt;/h3&gt;
&lt;p&gt;ES 搜索快的根本原因是&lt;strong&gt;倒排索引&lt;/strong&gt;：term → 文档列表。而文本进入倒排索引前要经过分析链：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;原始文本&amp;lt;br/&amp;gt;O Auto da Compadecida&amp;#34;] --&amp;gt; B[&amp;#34;Character Filter&amp;lt;br/&amp;gt;变音符号映射 á→a&amp;#34;]
    B --&amp;gt; C[&amp;#34;Tokenizer&amp;lt;br/&amp;gt;standard 分词&amp;#34;]
    C --&amp;gt; D[&amp;#34;Token Filter&amp;lt;br/&amp;gt;lowercase + stopword + stemmer&amp;#34;]
    D --&amp;gt; E[&amp;#34;词项 Term&amp;lt;br/&amp;gt;auto / compadecida&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;一个字段建索引时用哪个 analyzer、搜索时用哪个 analyzer，直接决定了&amp;quot;搜不搜得到&amp;quot;。&lt;strong&gt;这就是为什么 mapping 必须在灌数据之前设计好&lt;/strong&gt;——改 analyzer 意味着重建索引。&lt;/p&gt;
&lt;h3 id=&#34;22-近实时nrt与-refresh&#34;&gt;2.2 近实时（NRT）与 refresh&lt;/h3&gt;
&lt;p&gt;文档写入后先进内存缓冲区，默认 1 秒后 refresh 生成 segment 才可被搜索。批量灌数据时调大 &lt;code&gt;refresh_interval&lt;/code&gt;、或显式控制 &lt;code&gt;refresh&lt;/code&gt; 时机，是写入性能优化的第一手段——后面会看到项目里批量写入怎么用它。&lt;/p&gt;
&lt;h3 id=&#34;23-query-与-filter-的区别&#34;&gt;2.3 Query 与 Filter 的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query&lt;/strong&gt;：算相关性得分 &lt;code&gt;_score&lt;/code&gt;，用于全文匹配排序；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt;：只判断 yes/no，&lt;strong&gt;不算分、结果可被缓存&lt;/strong&gt;，用于精确过滤。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目里几乎所有查询都是&amp;quot;filter 套 query&amp;quot;的 bool 结构：关键词进 &lt;code&gt;must&lt;/code&gt;，租户、类型、软删除标记进 &lt;code&gt;filter&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三索引设计一个搜索索引--四个详情索引&#34;&gt;三、索引设计：一个搜索索引 + 四个详情索引&lt;/h2&gt;
&lt;h3 id=&#34;31-索引清单&#34;&gt;3.1 索引清单&lt;/h3&gt;
&lt;p&gt;项目中&lt;strong&gt;没有任何 &lt;code&gt;@Document&lt;/code&gt; 注解或 mapping JSON 文件&lt;/strong&gt;，索引 DDL 采用类似 Liquibase 的 changelog 形式管理（&lt;code&gt;changelog/elasticsearch/*.yaml&lt;/code&gt;，HTTP method + path + body，由外部部署工具执行），别名字段统一管理：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;代码中的别名&lt;/th&gt;
					&lt;th&gt;实体索引&lt;/th&gt;
					&lt;th&gt;分片&lt;/th&gt;
					&lt;th&gt;存什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_detail_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-detail-1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;媒资详情全集&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_person_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-person-detail&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;演职人员详情&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_artwork_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-media-artwork&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;剧照物料&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_trailer_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-media-trailer&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;预告片&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;search_engine_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-search-engine&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1 分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;C 端搜索/补全&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;别名与文档类的映射集中在一个枚举里（&lt;code&gt;OpenSearchIndexEnum&lt;/code&gt;），业务代码只认别名——这是&amp;quot;换索引不改代码&amp;quot;的基本功，比如 &lt;code&gt;ftmao-meta-detail-1&lt;/code&gt; 后面出现 &lt;code&gt;-2&lt;/code&gt;，只需要 reindex 后切别名。&lt;/p&gt;
&lt;h3 id=&#34;32-详情索引重存储轻检索&#34;&gt;3.2 详情索引：重存储、轻检索&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;meta_detail_index&lt;/code&gt; 的 mapping 有个鲜明特征：&lt;strong&gt;几乎所有字段都是 keyword，标题、剧情简介都是 keyword + nested，不参与分词检索&lt;/strong&gt;，大量 nested 对象（displayTitles / plots / posters / casts）配 &lt;code&gt;dynamic: false&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为什么？因为详情索引的职责是**&amp;ldquo;按 ID/结构化条件精确取数&amp;rdquo;**，不是全文搜索。keyword 不做分词，聚合、terms 过滤、精确匹配都快；&lt;code&gt;dynamic: false&lt;/code&gt; 防止上游数据乱塞字段把 mapping 撑爆（mapping explosion 是生产事故常客）。&lt;/p&gt;
&lt;h3 id=&#34;33-搜索索引整个项目最精彩的部分&#34;&gt;3.3 搜索索引：整个项目最精彩的部分&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;search_engine_index&lt;/code&gt; 只有 1 个分片（轻量、追求查询 RT），但它的 analysis 配置是整个项目技术含量最高的地方。以主搜索字段 &lt;code&gt;mainTitle&lt;/code&gt; 为例：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 摘自 changelog（简化）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;mainTitle&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;store&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;term_vector&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;with_positions_offsets  &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 为 fast-vector-highlighter 高亮做准备&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;en_es_pt                    &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 英/西/葡三语混合分词器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;fields&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;cut_word&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;en_es_pt }             &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 正常分词匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;all_letters&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;N_gram(4-15) }         &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 长 ngram：前缀/模糊匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;word_letters:{ analyzer: N_gram(2-10) }         &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 短 ngram：更激进的模糊匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;origin&lt;/span&gt;:      { &lt;span style=&#34;color:#f92672&#34;&gt;type: keyword, normalizer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;lowercase } &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 精确匹配/排序&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;配套自定义组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多语分析器 &lt;code&gt;en_es_pt&lt;/code&gt;&lt;/strong&gt;：standard tokenizer + 英语/西语/葡语三套 stopword + stemmer；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变音符号 char_filter&lt;/strong&gt;：把 &lt;code&gt;áéíóúàèç&lt;/code&gt; 等西葡变音符号映射为 ASCII——拉美用户搜 &lt;code&gt;compadecida&lt;/code&gt; 也能命中 &lt;code&gt;Compadecida&lt;/code&gt;，&lt;strong&gt;对多语言市场这是体验生死线&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ngram tokenizer&lt;/strong&gt; 一批（1–10、4–15 等不同窗口），&lt;code&gt;max_ngram_diff=14&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;completion suggester&lt;/strong&gt;：&lt;code&gt;suggest&lt;/code&gt; 字段配 &lt;code&gt;suggester_trigram&lt;/code&gt; 分析器，带 3 个 &lt;strong&gt;category context&lt;/strong&gt;（&lt;code&gt;markCate&lt;/code&gt; / &lt;code&gt;tenantCate&lt;/code&gt; / &lt;code&gt;comboCate&lt;/code&gt;）——自动补全可以按内容类型和租户隔离，A 运营商的用户不会看到 B 运营商专有的片名提示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;设计启示&lt;/strong&gt;：一个&amp;quot;能搜&amp;quot;的字段，实际需要四层子字段分工——正常分词管召回、长 ngram 管前缀联想、短 ngram 管纠错、keyword 管精确和排序。这也正是《Elasticsearch in Action》里&amp;quot;多字段映射&amp;quot;思想的生产级演绎。&lt;/p&gt;
&lt;h3 id=&#34;34-双索引架构为什么搜索和详情要分开&#34;&gt;3.4 双索引架构：为什么搜索和详情要分开&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 写入[&amp;#34;写入侧&amp;#34;]
        D[&amp;#34;MetaDetail 完整文档&amp;lt;br/&amp;gt;（几十 KB，含 nested 海报/演员/多语言）&amp;#34;] --&amp;gt; DETAIL[&amp;#34;详情索引&amp;lt;br/&amp;gt;6分片 重存储&amp;#34;]
        D --&amp;gt;|&amp;#34;裁剪：docId + mainTitle +&amp;lt;br/&amp;gt;suggest + 过滤字段 + 租户&amp;#34;| SEARCH[&amp;#34;搜索索引&amp;lt;br/&amp;gt;1分片 轻量&amp;#34;]
    end
    subgraph 查询[&amp;#34;查询侧&amp;#34;]
        U[&amp;#34;用户搜索 &amp;#39;auto&amp;#39;&amp;#34;] --&amp;gt;|&amp;#34;① matchSearch 命中&amp;lt;br/&amp;gt;返回 docId 列表&amp;#34;| SEARCH
        SEARCH --&amp;gt;|&amp;#34;② 按 docId mget&amp;#34;| DETAIL
        DETAIL --&amp;gt;|&amp;#34;组装完整详情返回&amp;#34;| U
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;搜索索引里&lt;strong&gt;不存详情&lt;/strong&gt;，只有：docId、mainTitle（及 ngram 子字段）、suggest、40+ 个 keyword 过滤字段（&lt;code&gt;ext-field-to-index-map&lt;/code&gt; 里配置，如媒资类型、地区、年份、分级）、租户字段。好处有三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;搜索索引小&lt;/strong&gt;，1 个分片 + 内存友好，C 端 QPS 扛得住；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;详情变更不用动搜索索引&lt;/strong&gt;——海报换了只 update 详情索引；搜索索引只在标题/过滤字段变化时更新；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两类索引可以独立扩缩容、独立重建&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代价也很明显：多一次 mget 回查、数据一致性要维护两份。这个项目用下一节的同步机制来兜底。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四数据同步mysql--es-的三条路径&#34;&gt;四、数据同步：MySQL → ES 的三条路径&lt;/h2&gt;
&lt;p&gt;ES 是&amp;quot;读模型&amp;quot;，MySQL 才是事实源。项目没用 Canal/MQ，而是自己实现了一套&lt;strong&gt;事务内直写 + 任务表轮询&lt;/strong&gt;的混合方案：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;业务写请求&amp;lt;br/&amp;gt;MetaService.save()&amp;#34;] --&amp;gt; B{&amp;#34;@Transactional&amp;#34;}
    B --&amp;gt; C[&amp;#34;① 写 MySQL 业务表&amp;#34;]
    B --&amp;gt; D[&amp;#34;② 写 MySQL 同步任务表&amp;lt;br/&amp;gt;MixedMetaSyncTask&amp;#34;]
    B --&amp;gt; E[&amp;#34;③ 同事务内直写详情索引&amp;lt;br/&amp;gt;bulkOperationByBatch(refresh=True)&amp;#34;]
    B --&amp;gt; F[&amp;#34;④ saveMetaToSearchEngine&amp;lt;br/&amp;gt;同步搜索索引&amp;#34;]
    E --&amp;gt; G{&amp;#34;ES 写失败?&amp;#34;}
    G --&amp;gt;|&amp;#34;是，事务回滚或留下任务&amp;#34;| H[&amp;#34;任务表残留记录&amp;#34;]
    D --&amp;gt; I[&amp;#34;PpMetaSyncJob&amp;lt;br/&amp;gt;@Scheduled 每 15s&amp;#34;]
    H --&amp;gt; I
    I --&amp;gt; J[&amp;#34;分布式锁&amp;lt;br/&amp;gt;executeWithLock&amp;#34;]
    J --&amp;gt; K[&amp;#34;MetaTaskProcessService&amp;lt;br/&amp;gt;分批 Lists.partition(batchSize)&amp;#34;]
    K --&amp;gt;|&amp;#34;searchSync.save(batch)&amp;#34;| L[&amp;#34;搜索索引&amp;#34;]
    M[&amp;#34;失败重试 Job&amp;lt;br/&amp;gt;每小时&amp;#34;] --&amp;gt; J&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三条路径各有分工：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同事务直写&lt;/strong&gt;（主路径）：保证&amp;quot;后台保存完，前台立刻能搜到&amp;quot;。写入时统一刷新 &lt;code&gt;updateTime&lt;/code&gt; 和 &lt;code&gt;lastModifiedIndicator&lt;/code&gt;（格式：&lt;code&gt;时间戳_docId&lt;/code&gt; 的版本游标，下一节详述）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务表 + 定时轮询&lt;/strong&gt;（兜底路径）：15 秒一轮，分布式锁防多实例并发，分批灌入；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败重试 Job&lt;/strong&gt;（保险）：每小时扫描失败任务重放。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么不纯用 MQ？&lt;/strong&gt; 这个系统的写入量级不大（媒资是低频高价值数据，不是日志），事务内直写的一致性收益远大于引入 MQ 的复杂度。这是&amp;quot;按场景选方案&amp;quot;而不是&amp;quot;按潮流选方案&amp;quot;的好例子。&lt;/p&gt;
&lt;h3 id=&#34;41-批量写入的性能细节&#34;&gt;4.1 批量写入的性能细节&lt;/h3&gt;
&lt;p&gt;工具类 &lt;code&gt;OpenSearchUtil.bulkUpsertDocuments&lt;/code&gt; 里有两个值得抄的实践：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 按配置的 bulkSize（默认 200）分批，避免单个 bulk 过大压垮集群&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 每批显式 refresh(WaitFor)：写完后“等 refresh 完成再返回”，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 保证同步接口的调用方&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;“&lt;/span&gt;写完立即可见&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;”，&lt;/span&gt;同时比 refresh&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;每文档刷新&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;省资源&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;对照第 2.2 节的 NRT 原理：&lt;code&gt;refresh=WaitFor&lt;/code&gt; 是吞吐和可见性之间的甜点——批量场景不要无脑 &lt;code&gt;refresh=true&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五查询实战从业务代码反推-es-用法&#34;&gt;五、查询实战：从业务代码反推 ES 用法&lt;/h2&gt;
&lt;h3 id=&#34;51-一切查询的起点软删除过滤&#34;&gt;5.1 一切查询的起点：软删除过滤&lt;/h3&gt;
&lt;p&gt;项目里文档不物理删除，而是软删除——所有读路径都要叠一层 &lt;code&gt;isDeleted=0&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// OpenSearchUtil.getUndeletedQuery：所有查询都要过这层包装&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query undeletedQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUndeletedQuery&lt;/span&gt;(boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;_toQuery&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 底层就是 bool.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(terms(isDeleted, &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;0&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;))&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;，&lt;/span&gt;可能再叠加 status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;1&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;已发布&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;业务侧的典型用法（&lt;code&gt;ArtworkService&lt;/code&gt;，按媒资 ID 查剧照）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query metaIdQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Query.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(q &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; q.&lt;span style=&#34;color:#a6e22e&#34;&gt;terms&lt;/span&gt;(t &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; t.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;metaId&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;terms&lt;/span&gt;(tv &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; tv.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;(Stream.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMetaId&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(FieldValue::of).&lt;span style=&#34;color:#a6e22e&#34;&gt;toList&lt;/span&gt;()))));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(metaIdQuery);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query undeletedQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUndeletedQuery&lt;/span&gt;(boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;_toQuery&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SortOptions sortOption &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; SortOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(s &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; s.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    f &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; f.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pos&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;(SortOrder.&lt;span style=&#34;color:#a6e22e&#34;&gt;Asc&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MediaArtwork&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; artworks &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;searchDoc&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OpenSearchIndexEnum.&lt;span style=&#34;color:#a6e22e&#34;&gt;MEDIA_ARTWORK&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    undeletedQuery, MediaArtwork.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPageId&lt;/span&gt;(), request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPageSize&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Collections.&lt;span style=&#34;color:#a6e22e&#34;&gt;singletonList&lt;/span&gt;(sortOption), totalCount);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这正是第 2.3 节&amp;quot;filter 不算分、可缓存&amp;quot;原理的落地：&lt;strong&gt;结构化条件全部进 filter&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;52-软删除的实现updatebyquery--painless-脚本&#34;&gt;5.2 软删除的实现：updateByQuery + Painless 脚本&lt;/h3&gt;
&lt;p&gt;删除不是 &lt;code&gt;delete&lt;/code&gt;，而是 &lt;code&gt;updateByQuery&lt;/code&gt; 打标记：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String script &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; ctx._source.isDeleted = 1;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ctx._source.updateTime = params.currentTime;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ctx._source.lastModifiedIndicator = params.indicatorPrefix + &amp;#39;_&amp;#39; + ctx._id&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UpdateByQueryRequest request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UpdateByQueryRequest.&lt;span style=&#34;color:#a6e22e&#34;&gt;Builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;(indexName).&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;(query)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;script&lt;/span&gt;(s &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; s.&lt;span style=&#34;color:#a6e22e&#34;&gt;inline&lt;/span&gt;(i &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; i.&lt;span style=&#34;color:#a6e22e&#34;&gt;source&lt;/span&gt;(finalScript)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;currentTime&amp;#34;&lt;/span&gt;, JsonData.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(currentTime))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;indicatorPrefix&amp;#34;&lt;/span&gt;, JsonData.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(String.&lt;span style=&#34;color:#a6e22e&#34;&gt;valueOf&lt;/span&gt;(modifyTime)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意脚本里同时更新了 &lt;code&gt;lastModifiedIndicator&lt;/code&gt;——&lt;strong&gt;软删除也是一次&amp;quot;变更&amp;quot;，必须让下游订阅方感知到&lt;/strong&gt;（下游才能同步删）。这个细节是订阅体系能闭环的关键。&lt;/p&gt;
&lt;h3 id=&#34;53-两阶段全文搜索&#34;&gt;5.3 两阶段全文搜索&lt;/h3&gt;
&lt;p&gt;C 端搜索入口 &lt;code&gt;SearchService.searchMedia&lt;/code&gt; 的分流逻辑：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (StringUtils.&lt;span style=&#34;color:#a6e22e&#34;&gt;isNotEmpty&lt;/span&gt;(fuzzyMatchingParams.&lt;span style=&#34;color:#a6e22e&#34;&gt;getKeyword&lt;/span&gt;())) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 有关键词：内部库全文检索 → 返回 docId + mark（类型）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;matchSearch&lt;/span&gt;(keyword, marks, pageId, pageSize, tenantIds, params);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 无关键词：纯结构化筛选（找片页、分类浏览）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;(marks, pageId, pageSize, tenantIds, params);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 按 mark 分流 person / meta，回查详情索引取完整数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;batchGetDocuments&lt;/span&gt;(PERSON_DETAIL.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(), personDocIds, PersonDetail.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;batchGetDocuments&lt;/span&gt;(META_DETAIL.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(), metaDocIds, MetaDetail.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;查询条件从哪来？API 层收的是 &lt;strong&gt;SQL 风格的 where 模板&lt;/strong&gt;（&lt;code&gt;MetaQuery.whereTemplate&lt;/code&gt; + &lt;code&gt;parameters&lt;/code&gt;），&lt;code&gt;WhereClauseUtil&lt;/code&gt; 用正则把 &lt;code&gt;in (:x)&lt;/code&gt;、&lt;code&gt;between and&lt;/code&gt;、&lt;code&gt;JSON_CONTAINS_ALL/ANY&lt;/code&gt; 解析成 ES 的 Condition（IN / GREATER_THAN 等），字段名经 &lt;code&gt;FieldNameMappingEnum&lt;/code&gt; 做外部名→索引字段名映射。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个设计的好处&lt;/strong&gt;：对前端/上游服务来说，查询语法是稳定的数据库风格，不暴露 ES DSL；ES mapping 改名、换引擎，外部无感。&lt;/p&gt;
&lt;h3 id=&#34;54-自动补全completion-suggester--租户隔离&#34;&gt;5.4 自动补全：completion suggester + 租户隔离&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// SearchService.suggest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Prompt&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; prompts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPromptList&lt;/span&gt;(keyword, marks, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, companyId, size, uuid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 底层 = 搜索索引 suggest 字段的 completion suggester，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// context 里带 tenantCate=companyId → 补全结果按运营商隔离&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 拿到 docId 后再回查详情索引组装标题和海报&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;举例如&amp;quot;用户输入 &lt;code&gt;int&lt;/code&gt;&amp;quot;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant U as 用户(运营商A)
    participant S as SearchService
    participant SE as 搜索索引 suggester
    participant D as 详情索引
    U-&amp;gt;&amp;gt;S: suggest(&amp;#34;int&amp;#34;, companyId=A)
    S-&amp;gt;&amp;gt;SE: completion suggest&amp;lt;br/&amp;gt;context: tenantCate=A
    SE--&amp;gt;&amp;gt;S: [&amp;#34;Interestelar&amp;#34;, &amp;#34;Intocáveis&amp;#34;]&amp;lt;br/&amp;gt;+ docId 列表
    S-&amp;gt;&amp;gt;D: mget(docIds)
    D--&amp;gt;&amp;gt;S: 标题 + 海报 URL
    S--&amp;gt;&amp;gt;U: 补全列表（带封面）&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;55-一个常被忽略的性能点只取-id-就别加载-_source&#34;&gt;5.5 一个常被忽略的性能点：只取 ID 就别加载 _source&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// fastGetDocumentById：判断文档存在/取 ID 时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// storedFields(&amp;#34;_id&amp;#34;) + size(1) + trackTotalHits(false)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 注释原话&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;：&lt;/span&gt;类似 MySQL 的 LIMIT 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;trackTotalHits(false)&lt;/code&gt;：不精确统计总数（默认只算到 10000），省掉一次全量计数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storedFields(&amp;quot;_id&amp;quot;)&lt;/code&gt;：不加载 &lt;code&gt;_source&lt;/code&gt;，网络传输量从几十 KB 降到几十 B。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;列表页&amp;quot;先查 ID 再回查&amp;quot;的场景，这两招能把响应时间砍掉一个量级。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六增量订阅es-作为变更数据源&#34;&gt;六、增量订阅：ES 作为&amp;quot;变更数据源&amp;quot;&lt;/h2&gt;
&lt;p&gt;下游服务（推荐、缓存、其他中台）需要感知媒资变更。项目没有用 CDC 或 MQ 广播，而是让下游&lt;strong&gt;基于水位游标主动拉取 ES&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant SUB as 下游订阅方
    participant Q as SubqueryService
    participant ES as 详情索引

    Note over SUB: 记录上次水位 W0&amp;lt;br/&amp;gt;（lastModifiedIndicator 字符串）
    SUB-&amp;gt;&amp;gt;Q: querySubQuery(from=W0, size=500)
    Q-&amp;gt;&amp;gt;ES: range(lastModifiedIndicator: gt W0, lt 当前时间-2s)&amp;lt;br/&amp;gt;+ 按 lastModifiedIndicator 升序
    ES--&amp;gt;&amp;gt;Q: 本批变更文档（含软删除的）
    Q--&amp;gt;&amp;gt;SUB: 文档 + 新水位 W1
    SUB-&amp;gt;&amp;gt;SUB: 落库/更新缓存，记录 W1
    Note over Q,ES: 对账：subQueryCount 用 count + sort desc + size(1)&amp;lt;br/&amp;gt;取区间最大水位，生产方/消费方核对数量&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个工程细节值得细说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;水位是字符串而非时间戳&lt;/strong&gt;：&lt;code&gt;lastModifiedIndicator = 时间戳 + &amp;quot;_&amp;quot; + docId&lt;/code&gt;。纯时间戳会在同一毫秒内有多个文档时漏数；拼上 docId 后字典序唯一，range &lt;code&gt;gt&lt;/code&gt; 翻页不会漏、不会重。这是项目&lt;strong&gt;用手写游标替代 &lt;code&gt;search_after&lt;/code&gt;&lt;/strong&gt; 的原因——行为完全可控，且不依赖 ES 的 PIT（point in time）上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;lt 当前时间 - 2s&lt;/code&gt; 的回调偏移&lt;/strong&gt;：规避&amp;quot;刚写入还没 refresh 可见&amp;quot;的窗口（第 2.2 节 NRT 原理的直接应用），宁可晚 2 秒，不可漏数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;软删除文档也带着新水位出现&lt;/strong&gt;，下游据此删除本地副本——和第 5.2 节的脚本闭环。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七多租户设计docid-里藏着租户&#34;&gt;七、多租户设计：docId 里藏着租户&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// OpenSearchUtil.getDocId：文档 ID = companyId + &amp;#34;#&amp;#34; + metaId&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 例&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;：&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1001#mv_8848123&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;、&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public#mv_8848123&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;公共库&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;同一部影片，A 运营商和 B 运营商是&lt;strong&gt;两份文档&lt;/strong&gt;（各自的标题、定价、上架状态可能不同），天然物理隔离；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sourceType&lt;/code&gt; 字段区分 PRIVATE（私有）/ PUBLIC（公共）/ MIXED（混合），公共内容用 &lt;code&gt;public#&lt;/code&gt; 前缀，查询时按租户策略合并；&lt;/li&gt;
&lt;li&gt;所有查询 filter 里都带 &lt;code&gt;companyId&lt;/code&gt; / &lt;code&gt;tenant_id&lt;/code&gt;，补全用 context 隔离（第 5.4 节）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;经验&lt;/strong&gt;：多租户搜索系统，把租户编进 docId 比单纯加过滤字段更稳——docId 冲突本身就是一道防串租户的保险。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八回到原理这个项目验证了哪些书里的论断&#34;&gt;八、回到原理：这个项目验证了哪些书里的论断&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;书中原理&lt;/th&gt;
					&lt;th&gt;项目里的体现&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Mapping 先行，改 analyzer 要重建索引&lt;/td&gt;
					&lt;td&gt;索引 DDL 用 changelog 版本化管理，上线流程化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Filter 不算分、可缓存&lt;/td&gt;
					&lt;td&gt;租户/类型/软删除/状态全部进 filter，只有关键词算分&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多字段映射解决&amp;quot;一个字段多种搜法&amp;quot;&lt;/td&gt;
					&lt;td&gt;mainTitle 四层子字段：分词/长ngram/短ngram/keyword&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;NRT 的 refresh 是性能旋钮&lt;/td&gt;
					&lt;td&gt;批量写入用 &lt;code&gt;refresh=WaitFor&lt;/code&gt;；订阅端留 2s 偏移&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;深度翻页要避免 from+size 深分页&lt;/td&gt;
					&lt;td&gt;手写 &lt;code&gt;lastModifiedIndicator&lt;/code&gt; 游标替代 search_after&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;聚合昂贵，慎用&lt;/td&gt;
					&lt;td&gt;项目几乎零聚合；取最大值用 sort+size(1) 替代 max agg&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;分片数创建时定死，规划要前置&lt;/td&gt;
					&lt;td&gt;详情索引 6 分片按数据增长预估；搜索索引刻意 1 分片&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结&#34;&gt;九、总结&lt;/h2&gt;
&lt;p&gt;如果把这套架构压缩成一张图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    W[&amp;#34;写路径&amp;lt;br/&amp;gt;MySQL 事务&amp;lt;br/&amp;gt;+ 任务表兜底&amp;#34;] --&amp;gt; ES
    subgraph ES[&amp;#34;OpenSearch&amp;#34;]
        S[&amp;#34;搜索索引&amp;lt;br/&amp;gt;小而快：ngram+suggester&amp;lt;br/&amp;gt;+租户 context&amp;#34;]
        D[&amp;#34;详情索引&amp;lt;br/&amp;gt;大而全：keyword+nested&amp;lt;br/&amp;gt;dynamic:false&amp;#34;]
    end
    ES --&amp;gt; R1[&amp;#34;读路径 ①&amp;lt;br/&amp;gt;搜索/补全 → docId&amp;#34;]
    R1 --&amp;gt; R2[&amp;#34;读路径 ②&amp;lt;br/&amp;gt;mget 详情组装&amp;#34;]
    ES --&amp;gt; R3[&amp;#34;订阅路径&amp;lt;br/&amp;gt;水位游标增量拉取&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三条核心经验：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;索引按&amp;quot;查询模式&amp;quot;而不是&amp;quot;数据表&amp;quot;来设计&lt;/strong&gt;：搜索索引和详情索引分开，一个为召回优化（ngram、suggester、多语分析器），一个为取数优化（keyword、nested、禁动态字段）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性靠机制不靠运气&lt;/strong&gt;：事务内直写是主路径，任务表 + 定时 Job + 失败重试是兜底，水位游标让下游最终一致——三层防护各管一段；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所有&amp;quot;黑科技&amp;quot;都是基本原理的应用&lt;/strong&gt;：refresh 偏移、filter 缓存、storedFields 取 ID、游标翻页……吃透《Elasticsearch in Action》那套原理，生产方案都是它的排列组合。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：Radu Gheorghe, Matthew Lee Hinman, Roy Russo. 《Elasticsearch in Action》. Manning Publications, 2015；ftmao-meta 项目源码（OpenSearchConfig / OpenSearchUtil / SearchService / SubqueryService / changelog/elasticsearch）。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《Elasticsearch 权威指南》详解：零基础读懂 ES 的核心知识体系</title>
      <link>https://ftmao.com/blog/bookstore/elasticsearch-definitive-guide-notes/</link>
      <pubDate>Sat, 20 Jun 2020 09:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/elasticsearch-definitive-guide-notes/</guid>
      <description>&lt;h1 id=&#34;elasticsearch-权威指南详解零基础读懂-es-的核心知识体系&#34;&gt;《Elasticsearch 权威指南》详解：零基础读懂 ES 的核心知识体系&lt;/h1&gt;
&lt;p&gt;《Elasticsearch 权威指南》（Elasticsearch: The Definitive Guide，Clinton Gormley &amp;amp; Zachary Tong 著）是学习 Elasticsearch 公认的第一本书。中文版由 looly 等人组织社区翻译。它不是 API 手册，而是沿着&amp;quot;&lt;strong&gt;为什么这样设计&lt;/strong&gt;&amp;ldquo;的路线，把 ES 从安装、文档 CRUD 一路讲到分布式内核、分词、相关性算分和集群运维。&lt;/p&gt;
&lt;p&gt;这本书基于 ES 1.x/2.x 时代写成，个别细节（如 type 概念、TF/IDF 默认评分）在新版本有变化，但&lt;strong&gt;核心思想至今不变&lt;/strong&gt;。本文面向从未读过这本书的人，按&amp;quot;基础概念 → 分布式原理 → 搜索 → 语言处理 → 聚合 → 建模 → 运维&amp;quot;的顺序完整梳理，配图解和可直接运行的实例。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一elasticsearch-是什么先建立直觉&#34;&gt;一、Elasticsearch 是什么：先建立直觉&lt;/h2&gt;
&lt;p&gt;书的开篇用一句话定义 ES：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Elasticsearch 是一个&lt;strong&gt;分布式的、实时的文档存储与搜索引擎&lt;/strong&gt;，基于 Apache Lucene 构建，通过 RESTful API 暴露 Lucene 的全部威力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lucene 本身是一个 Java 搜索库，强大但难用——ES 的价值在于把 Lucene 包装成一个可以水平扩展、开箱即用的分布式系统。书中列举的典型用户包括 Wikipedia（全文搜索 + 高亮 + 输入即搜 + 拼写纠错）、GitHub（检索 1300 亿行代码）、StackOverflow（&amp;ldquo;你可能想问&amp;quot;的 more-like-this 推荐）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与关系型数据库的概念对照&lt;/strong&gt;（这是全书最重要的入门表格）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;关系型数据库&lt;/th&gt;
					&lt;th&gt;Elasticsearch&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Database（库）&lt;/td&gt;
					&lt;td&gt;Index（索引）&lt;/td&gt;
					&lt;td&gt;逻辑命名空间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Table（表）&lt;/td&gt;
					&lt;td&gt;Type（类型）&lt;/td&gt;
					&lt;td&gt;⚠️ 新版已废弃，理解旧书时需要&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Row（行）&lt;/td&gt;
					&lt;td&gt;Document（文档）&lt;/td&gt;
					&lt;td&gt;一条 JSON 记录&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Column（列）&lt;/td&gt;
					&lt;td&gt;Field（字段）&lt;/td&gt;
					&lt;td&gt;文档的属性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Schema（表结构）&lt;/td&gt;
					&lt;td&gt;Mapping（映射）&lt;/td&gt;
					&lt;td&gt;字段类型定义&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SQL&lt;/td&gt;
					&lt;td&gt;Query DSL&lt;/td&gt;
					&lt;td&gt;查询语言&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;index（索引，动词）&lt;/td&gt;
					&lt;td&gt;index（索引，动词）&lt;/td&gt;
					&lt;td&gt;把文档写入&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关键区别&lt;/strong&gt;：数据库假设&amp;quot;每行结构相同&amp;rdquo;，ES 的文档是无模式的 JSON（虽然实际上有 mapping 约束）；数据库为&amp;quot;精确存取&amp;quot;优化，ES 为&amp;rdquo;&lt;strong&gt;相关性检索&lt;/strong&gt;&amp;ldquo;优化——它回答的问题不是&amp;quot;有没有&amp;rdquo;，而是&amp;quot;&lt;strong&gt;有多相关&lt;/strong&gt;&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;11-十分钟跑起来书中的第一个例子&#34;&gt;1.1 十分钟跑起来：书中的第一个例子&lt;/h3&gt;
&lt;p&gt;全书第一章用一家虚构公司 megacorp 的员工数据贯穿：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 写入一个员工文档（PUT = 指定 ID 创建/覆盖）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PUT /megacorp/employee/1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;first_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;last_name&amp;#34;&lt;/span&gt;:  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Smith&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;:        25,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;about&amp;#34;&lt;/span&gt;:      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I love to go rock climbing&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;interests&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sports&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;music&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 取回（GET）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 轻量搜索：查询字符串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search?q&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;last_name:Smith
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 正式搜索：Query DSL（JSON 请求体）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;last_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Smith&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意这里&lt;strong&gt;没有建库、没有建表、没有定义字段类型&lt;/strong&gt;——这就是&amp;quot;无模式&amp;quot;体验。但书里马上提醒：无模式是假象，ES 在背后做了&lt;strong&gt;动态映射（dynamic mapping）&lt;/strong&gt;，第一次见 &lt;code&gt;&amp;quot;age&amp;quot;: 25&lt;/code&gt; 就推断 age 是 long。生产环境应该显式定义 mapping（第四章详解）。&lt;/p&gt;
&lt;h3 id=&#34;12-一个展示相关性的例子&#34;&gt;1.2 一个展示&amp;quot;相关性&amp;quot;的例子&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bool&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;must&amp;#34;&lt;/span&gt;:     &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;about&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rock climbing&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;:   &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;range&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;返回结果中每个文档带一个 &lt;code&gt;_score&lt;/code&gt;（相关性得分），按得分降序排列。&lt;code&gt;must&lt;/code&gt; 里的 match 参与算分，&lt;code&gt;filter&lt;/code&gt; 里的 range 只过滤不算分——&lt;strong&gt;这是全书反复出现的 query/filter 二分法&lt;/strong&gt;，第六章详解。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二分布式内核es-为什么能水平扩展&#34;&gt;二、分布式内核：ES 为什么能水平扩展&lt;/h2&gt;
&lt;p&gt;这是全书的硬核部分，也是 ES 区别于&amp;quot;单机 Lucene&amp;quot;的根本。&lt;/p&gt;
&lt;h3 id=&#34;21-集群节点与分片&#34;&gt;2.1 集群、节点与分片&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph CLUSTER[&amp;#34;集群 cluster（一个 cluster.name）&amp;#34;]
        N1[&amp;#34;节点 Node 1&amp;lt;br/&amp;gt;（一个 ES 进程）&amp;#34;]
        N2[&amp;#34;节点 Node 2&amp;#34;]
        N3[&amp;#34;节点 Node 3&amp;#34;]
    end
    subgraph IDX[&amp;#34;索引 blogs（3 主分片 × 1 副本）&amp;#34;]
        P0[&amp;#34;主分片 P0&amp;#34;] --- R0[&amp;#34;副本分片 R0&amp;#34;]
        P1[&amp;#34;主分片 P1&amp;#34;] --- R1[&amp;#34;副本分片 R1&amp;#34;]
        P2[&amp;#34;主分片 P2&amp;#34;] --- R2[&amp;#34;副本分片 R2&amp;#34;]
    end
    N1 -.承载.- P0
    N1 -.承载.- R1
    N2 -.承载.- P1
    N2 -.承载.- R2
    N3 -.承载.- P2
    N3 -.承载.- R0
    style CLUSTER fill:#e8f0fe&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分片（shard）是 ES 分布式能力的最小单元&lt;/strong&gt;：一个分片就是一个完整的 Lucene 实例。索引只是&amp;quot;一组分片的逻辑命名空间&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主分片数在创建索引时定死&lt;/strong&gt;（路由哈希依赖它），副本分片数可以随时调整。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主副本不允许在同一节点&lt;/strong&gt;——这样任意一台机器宕机，数据不丢、搜索不中断。&lt;/li&gt;
&lt;li&gt;集群健康三态：&lt;code&gt;green&lt;/code&gt;（所有主副本都就位）/ &lt;code&gt;yellow&lt;/code&gt;（主分片齐了，副本缺）/ &lt;code&gt;red&lt;/code&gt;（有主分片丢失，数据不可用）。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /_cluster/health   &lt;span style=&#34;color:#75715e&#34;&gt;# 全书最常用的运维命令&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;22-扩容为什么是免费的&#34;&gt;2.2 扩容为什么是&amp;quot;免费&amp;quot;的&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 三节点[&amp;#34;3 节点&amp;#34;]
        A1[&amp;#34;P0 R1&amp;#34;] 
        B1[&amp;#34;P1 R2&amp;#34;]
        C1[&amp;#34;P2 R0&amp;#34;]
    end
    subgraph 加一节点[&amp;#34;加入第 4 节点后：自动再平衡&amp;#34;]
        A2[&amp;#34;P0&amp;#34;]
        B2[&amp;#34;P1 R0&amp;#34;]
        C2[&amp;#34;P2&amp;#34;]
        D2[&amp;#34;R1 R2&amp;#34;]
    end
    三节点 --&amp;gt;|&amp;#34;分片自动迁移&amp;lt;br/&amp;gt;无需停机&amp;#34;| 加一节点&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分片是 Lucene 实例，搬分片 = 搬一个独立的小搜索引擎。ES 自动把分片挪到新节点，流量自动均摊。&lt;strong&gt;垂直扩容（换大机器）之外，ES 天生支持水平扩容（加机器）&lt;/strong&gt;——这是书里&amp;quot;scale up vs scale out&amp;quot;一节的核心论点。&lt;/p&gt;
&lt;h3 id=&#34;23-文档是怎么找到家的路由&#34;&gt;2.3 文档是怎么找到家的：路由&lt;/h3&gt;
&lt;p&gt;写入/读取一个文档时，ES 用它定位分片：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;shard = hash(routing) % number_of_primary_shards&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;routing&lt;/code&gt; 默认是文档的 &lt;code&gt;_id&lt;/code&gt;，也可以自定义（比如按用户 ID 路由，让同一用户的数据落在同一分片）。&lt;strong&gt;这解释了主分片数为什么不能改&lt;/strong&gt;：改了模数，所有已有文档的哈希结果全部失效。&lt;/p&gt;
&lt;h3 id=&#34;24-写入与读取的分布式流程&#34;&gt;2.4 写入与读取的分布式流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant C as 客户端
    participant N as 协调节点&amp;lt;br/&amp;gt;（收到请求的任意节点）
    participant P as 主分片所在节点
    participant R1 as 副本节点1
    participant R2 as 副本节点2

    Note over C,N: 写入流程
    C-&amp;gt;&amp;gt;N: PUT /blogs/post/123
    N-&amp;gt;&amp;gt;N: 计算路由 hash(_id)%3 → P1
    N-&amp;gt;&amp;gt;P: 转发写入
    P-&amp;gt;&amp;gt;P: 本地写 Lucene + translog
    par 并行同步副本
        P-&amp;gt;&amp;gt;R1: 复制写入
        P-&amp;gt;&amp;gt;R2: 复制写入
    end
    R1--&amp;gt;&amp;gt;P: ack
    R2--&amp;gt;&amp;gt;P: ack
    P--&amp;gt;&amp;gt;N: 成功
    N--&amp;gt;&amp;gt;C: 201 + _version

    Note over C,N: 读取流程
    C-&amp;gt;&amp;gt;N: GET /blogs/post/123
    N-&amp;gt;&amp;gt;N: 路由计算 → 分片 P1
    N-&amp;gt;&amp;gt;R2: 主/副本轮询负载均衡
    R2--&amp;gt;&amp;gt;N: 文档
    N--&amp;gt;&amp;gt;C: 200 + _source&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个关键设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;任何节点都能当协调节点&lt;/strong&gt;——没有单点入口；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写走主分片，读可以走主或副本&lt;/strong&gt;——副本不仅容灾，还分摊读流量。加副本 = 加读吞吐。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三近实时的秘密refreshtranslog-与段合并&#34;&gt;三、近实时的秘密：refresh、translog 与段合并&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;文档写入后为什么默认 1 秒才能搜到？&amp;ldquo;回答这个问题需要理解 Lucene 的存储模型。&lt;/p&gt;
&lt;h3 id=&#34;31-倒排索引与不可变段&#34;&gt;3.1 倒排索引与不可变段&lt;/h3&gt;
&lt;p&gt;ES 快的根源是&lt;strong&gt;倒排索引&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 词典[&amp;#34;Term Dictionary&amp;#34;]
        T1[&amp;#34;climbing&amp;#34;]
        T2[&amp;#34;music&amp;#34;]
        T3[&amp;#34;rock&amp;#34;]
    end
    subgraph 倒排表[&amp;#34;Posting List&amp;#34;]
        L1[&amp;#34;doc 1, doc 5&amp;#34;]
        L2[&amp;#34;doc 1, doc 3&amp;#34;]
        L3[&amp;#34;doc 1&amp;#34;]
    end
    T1 --&amp;gt; L1
    T2 --&amp;gt; L2
    T3 --&amp;gt; L3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Lucene 把倒排索引持久化为&lt;strong&gt;段（segment）&lt;/strong&gt;，段一旦写出就&lt;strong&gt;不可变（immutable）&lt;/strong&gt;。不可变性带来一串好处：不需要锁、可以进文件系统缓存、删除只需打标记。代价是：&lt;strong&gt;更新 = 删旧 + 建新&lt;/strong&gt;，新写的文档必须先攒成新段才能被搜索。&lt;/p&gt;
&lt;h3 id=&#34;32-完整链路&#34;&gt;3.2 完整链路&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    W[&amp;#34;文档写入请求&amp;#34;] --&amp;gt; MEM[&amp;#34;内存缓冲区&amp;#34;]
    W --&amp;gt; TL[&amp;#34;translog&amp;lt;br/&amp;gt;（先写 WAL，防宕机丢数据）&amp;#34;]
    MEM --&amp;gt;|&amp;#34;refresh&amp;lt;br/&amp;gt;默认每 1 秒&amp;#34;| SEG[&amp;#34;新 segment&amp;lt;br/&amp;gt;写入文件系统缓存&amp;lt;br/&amp;gt;→ 立即可被搜索&amp;#34;]
    SEG --&amp;gt;|&amp;#34;flush&amp;lt;br/&amp;gt;translog 满或定时&amp;#34;| DISK[&amp;#34;fsync 落盘&amp;lt;br/&amp;gt;清空 translog&amp;#34;]
    SEG -.-&amp;gt;|&amp;#34;后台 merge&amp;#34;| BIG[&amp;#34;小段合并成大段&amp;lt;br/&amp;gt;物理清除已删文档&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个动作的分工：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;refresh&lt;/strong&gt;：内存 → 新的可搜索 segment（在文件系统缓存里，不落盘）。这就是&amp;quot;近实时（NRT）&amp;ldquo;的来源——默认 1 秒延迟。批量导入时可以调大 &lt;code&gt;refresh_interval&lt;/code&gt; 换吞吐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;translog&lt;/strong&gt;：预写日志。每个写入先记 translog，节点宕机后用它重放还没 flush 的数据。这是&amp;quot;ES 不丢数据&amp;quot;的底气。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;flush&lt;/strong&gt;：真正 fsync 落盘 + 清空 translog。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;merge&lt;/strong&gt;：小段太多会拖慢搜索（每个查询都要遍历所有段），后台线程持续把小段合并成大段，并顺带物理删除被标记删除的文档。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;给初学者的记忆法&lt;/strong&gt;：refresh 管&amp;quot;搜得到&amp;rdquo;，flush 管&amp;quot;丢不了&amp;rdquo;，merge 管&amp;quot;搜得快&amp;rdquo;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四映射与分析数据进-es-之前发生了什么&#34;&gt;四、映射与分析：数据进 ES 之前发生了什么&lt;/h2&gt;
&lt;h3 id=&#34;41-mapping字段的类型决定它能怎么被查&#34;&gt;4.1 Mapping：字段的类型决定它能怎么被查&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;PUT&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/blogs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;mappings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ik_max_word&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;:   { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;views&amp;#34;&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;integer&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;created&amp;#34;&lt;/span&gt;:  { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;date&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;最核心的类型二分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;text&lt;/code&gt;&lt;/strong&gt;：全文字段，会被&lt;strong&gt;分词&lt;/strong&gt;后存入倒排索引，用于 &lt;code&gt;match&lt;/code&gt; 全文检索；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;keyword&lt;/code&gt;&lt;/strong&gt;：精确值字段，&lt;strong&gt;原样&lt;/strong&gt;入索引，用于过滤、排序、聚合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书中金句：&amp;quot;&lt;strong&gt;exact value 和 full text 的区别，是 SQL 世界和搜索世界的分界&lt;/strong&gt;&amp;quot;。用 keyword 字段做 match 全文搜索（或反过来）是新手最常见的错误。&lt;/p&gt;
&lt;p&gt;⚠️ 版本提示：原书写作时还是 &lt;code&gt;string&lt;/code&gt; + &lt;code&gt;analyzed/not_analyzed&lt;/code&gt; 的写法，ES 5.x 起拆分为 &lt;code&gt;text&lt;/code&gt;/&lt;code&gt;keyword&lt;/code&gt;，思想完全一致。&lt;/p&gt;
&lt;h3 id=&#34;42-分析链分词是怎么发生的&#34;&gt;4.2 分析链：分词是怎么发生的&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;原始文本&amp;lt;br/&amp;gt;&amp;#39;The Quick Brown Fox!&amp;#39;&amp;#34;] --&amp;gt; CF[&amp;#34;Character Filter&amp;lt;br/&amp;gt;字符过滤&amp;lt;br/&amp;gt;（去 HTML、字符映射）&amp;#34;]
    CF --&amp;gt; TK[&amp;#34;Tokenizer&amp;lt;br/&amp;gt;分词器&amp;lt;br/&amp;gt;&amp;#39;The&amp;#39;,&amp;#39;Quick&amp;#39;,&amp;#39;Brown&amp;#39;,&amp;#39;Fox&amp;#39;&amp;#34;]
    TK --&amp;gt; TF[&amp;#34;Token Filter&amp;lt;br/&amp;gt;词项过滤器&amp;lt;br/&amp;gt;lowercase → 停用词 → 词干提取&amp;#34;]
    TF --&amp;gt; R[&amp;#34;最终词项&amp;lt;br/&amp;gt;&amp;#39;quick&amp;#39;,&amp;#39;brown&amp;#39;,&amp;#39;fox&amp;#39;&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Character Filter&lt;/strong&gt;：分词前做字符级预处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tokenizer&lt;/strong&gt;：把文本切成词项（一个分析器&lt;strong&gt;只能有一个&lt;/strong&gt; tokenizer）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Filter&lt;/strong&gt;：对词项再加工，可串联多个（lowercase、停用词、同义词、词干提取）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内置的 &lt;code&gt;standard&lt;/code&gt; 分析器 = standard tokenizer + lowercase + stop。可以用 &lt;code&gt;_analyze&lt;/code&gt; API 眼见为实：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;POST /_analyze
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;The Quick Brown Fox!&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# → the, quick, brown, fox（&amp;#39;The&amp;#39; 被小写化）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;中文场景&lt;/strong&gt;：标准分词器会把中文逐字切开，效果很差。社区方案是 IK 分词器（&lt;code&gt;ik_max_word&lt;/code&gt; 细粒度建索引、&lt;code&gt;ik_smart&lt;/code&gt; 粗粒度搜索），这是中文 ES 项目的标配。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;索引时与搜索时的分析必须配套&lt;/strong&gt;：文档里的 &lt;code&gt;&amp;quot;Fox&amp;quot;&lt;/code&gt; 入库时被分析成 &lt;code&gt;fox&lt;/code&gt;；搜索 &lt;code&gt;&amp;quot;FOX&amp;quot;&lt;/code&gt; 时查询词也会被同一个分析器处理成 &lt;code&gt;fox&lt;/code&gt; 去匹配——&lt;strong&gt;这就是为什么 ES 搜索&amp;quot;不区分大小写&amp;quot;&lt;/strong&gt;：不是查询忽略了大小写，而是两边都被 lowercased 了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五搜索query-dsl-的全景&#34;&gt;五、搜索：Query DSL 的全景&lt;/h2&gt;
&lt;h3 id=&#34;51-query-与-filter全书最重要的性能概念&#34;&gt;5.1 Query 与 Filter：全书最重要的性能概念&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    Q[&amp;#34;Query 上下文&amp;lt;br/&amp;gt;&amp;#39;这个文档和查询有多相关？&amp;#39;&amp;#34;]
    F[&amp;#34;Filter 上下文&amp;lt;br/&amp;gt;&amp;#39;这个文档是否匹配？&amp;#39;&amp;#34;]
    Q --&amp;gt; A1[&amp;#34;计算 _score&amp;lt;br/&amp;gt;结果按相关性排序&amp;#34;]
    F --&amp;gt; A2[&amp;#34;只答 yes/no&amp;lt;br/&amp;gt;不算分、结果可被缓存&amp;#34;]
    style Q fill:#fef3e8
    style F fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;经验法则：能用 filter 就不用 query&lt;/strong&gt;。过滤条件（状态、时间范围、ID 列表）放 filter，既省算分开销又能命中 bitset 缓存。&lt;/p&gt;
&lt;h3 id=&#34;52-常用查询速查&#34;&gt;5.2 常用查询速查&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;查询&lt;/th&gt;
					&lt;th&gt;用途&lt;/th&gt;
					&lt;th&gt;例子&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;match&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全文搜索（会分词）&lt;/td&gt;
					&lt;td&gt;标题含&amp;quot;登山 装备&amp;quot;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;term&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;精确匹配（不分词）&lt;/td&gt;
					&lt;td&gt;status 等于 &amp;ldquo;published&amp;rdquo;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;terms&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多值精确匹配&lt;/td&gt;
					&lt;td&gt;标签 in [&amp;ldquo;科幻&amp;rdquo;,&amp;ldquo;动作&amp;rdquo;]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;range&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;范围&lt;/td&gt;
					&lt;td&gt;age ≥ 30&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;exists&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;字段非空&lt;/td&gt;
					&lt;td&gt;必须有 poster 字段&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;multi_match&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;跨多字段全文&lt;/td&gt;
					&lt;td&gt;同时在标题和简介里搜&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;match_phrase&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;短语匹配（词序相邻）&lt;/td&gt;
					&lt;td&gt;搜 &amp;ldquo;rock climbing&amp;rdquo; 不拆词&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;组合一切&lt;/td&gt;
					&lt;td&gt;见下&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;53-bool-查询乐高积木&#34;&gt;5.3 bool 查询：乐高积木&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GET&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/megacorp/employee/_search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;bool&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;must&amp;#34;&lt;/span&gt;:     [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;about&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rock climbing&amp;#34;&lt;/span&gt; } } ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;should&amp;#34;&lt;/span&gt;:   [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;interests&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;music&amp;#34;&lt;/span&gt; } } ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;must_not&amp;#34;&lt;/span&gt;: [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;term&amp;#34;&lt;/span&gt;:  { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;last_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;smith&amp;#34;&lt;/span&gt; } } ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;:   [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;range&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;gte&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; } } } ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;must&lt;/code&gt;：必须匹配，参与算分；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;should&lt;/code&gt;：最好匹配，匹配上加分（没有 must/filter 时至少命中一个）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;must_not&lt;/code&gt;：必须不匹配，不参与算分（在 filter 上下文执行）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;：必须匹配，不参与算分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所有复杂查询都是这四块积木的嵌套&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;54-相关性算分_score-是怎么来的&#34;&gt;5.4 相关性算分：_score 是怎么来的&lt;/h3&gt;
&lt;p&gt;书里讲了经典的 &lt;strong&gt;TF/IDF&lt;/strong&gt; 模型（新版本默认换成 BM25，思想相同，多了文档长度归一化）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;词频 TF&lt;/strong&gt;：词在本文档出现越多，越相关；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆文档频率 IDF&lt;/strong&gt;：词在所有文档中越罕见，区分度越高（&amp;ldquo;的&amp;quot;不值钱，&amp;ldquo;攀岩&amp;quot;值钱）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字段长度&lt;/strong&gt;：字段越短，匹配越&amp;quot;精悍&amp;rdquo;，得分越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对结果有疑问时用 &lt;code&gt;_explain&lt;/code&gt; API 看算分过程——这是调搜索体验的第一件工具。&lt;/p&gt;
&lt;h3 id=&#34;55-深分页陷阱&#34;&gt;5.5 深分页陷阱&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /_search?from&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10000&amp;amp;size&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;# ⚠️ 危险&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;分布式搜索是 &lt;strong&gt;scatter-gather&lt;/strong&gt; 模式：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant C as 客户端
    participant CO as 协调节点
    participant S0 as 分片0
    participant S1 as 分片1
    participant S2 as 分片2
    C-&amp;gt;&amp;gt;CO: from=10000, size=10
    par scatter
        CO-&amp;gt;&amp;gt;S0: 各分片先取 Top 10010
        CO-&amp;gt;&amp;gt;S1: 各分片先取 Top 10010
        CO-&amp;gt;&amp;gt;S2: 各分片先取 Top 10010
    end
    S0--&amp;gt;&amp;gt;CO: 10010 条
    S1--&amp;gt;&amp;gt;CO: 10010 条
    S2--&amp;gt;&amp;gt;CO: 10010 条
    CO-&amp;gt;&amp;gt;CO: 归并 30030 条，取 10001~10010
    CO--&amp;gt;&amp;gt;C: 10 条&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;取第 1001 页，要在每个分片各取 10010 条再归并。&lt;strong&gt;深度翻页成本线性爆炸&lt;/strong&gt;，书里的忠告是&amp;quot;不要在搜索场景做深翻页&amp;rdquo;；确实需要遍历全量时，用 &lt;code&gt;scroll&lt;/code&gt;（游标快照）或新版本的 &lt;code&gt;search_after&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六处理人类语言&#34;&gt;六、处理人类语言&lt;/h2&gt;
&lt;p&gt;这一部分是本书区别于普通教程的精华：搜索的本质对手是&lt;strong&gt;自然语言的模糊性&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语言分析器&lt;/strong&gt;：英语词干提取（&lt;code&gt;foxes&lt;/code&gt;→&lt;code&gt;fox&lt;/code&gt;）、德语的复合词拆分、中文/日文的无空格分词。每种语言的正确处理方式完全不同——&lt;strong&gt;选对语言分析器比调任何参数都重要&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拼写纠错（did-you-mean）&lt;/strong&gt;：&lt;code&gt;term suggester&lt;/code&gt; / &lt;code&gt;phrase suggester&lt;/code&gt;，&amp;ldquo;你搜的是不是 elasticsearch？&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入即搜（search-as-you-type）&lt;/strong&gt;：用 &lt;code&gt;edge ngram&lt;/code&gt;（前缀 ngram）在索引时把 &lt;code&gt;&amp;quot;elasticsearch&amp;quot;&lt;/code&gt; 切成 &lt;code&gt;e, el, ela, ...&lt;/code&gt;，用户每敲一个字母就能命中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;近似匹配&lt;/strong&gt;：&lt;code&gt;match&lt;/code&gt; 的 &lt;code&gt;fuzziness&lt;/code&gt; 参数基于编辑距离，容忍拼写错误（&lt;code&gt;searching&lt;/code&gt; 打成 &lt;code&gt;serching&lt;/code&gt; 也能命中）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;近似短语&lt;/strong&gt;：&lt;code&gt;match_phrase&lt;/code&gt; 的 &lt;code&gt;slop&lt;/code&gt; 参数允许词与词之间隔着几个词仍算匹配。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一部分的共同心法：&lt;strong&gt;召回率与准确率的权衡&lt;/strong&gt;。ngram 开得越大、fuzziness 越高，召回越好但噪音越多；工程上常用&amp;quot;多层子字段&amp;quot;（精确字段 + 分词字段 + ngram 字段）分层兜底。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七聚合从搜索到分析&#34;&gt;七、聚合：从搜索到分析&lt;/h2&gt;
&lt;p&gt;搜索回答&amp;quot;哪些文档匹配&amp;quot;，&lt;strong&gt;聚合（aggregations）&lt;/strong&gt; 回答&amp;quot;数据整体长什么样&amp;quot;。聚合分两大类，可以无限嵌套：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;桶（Bucket）聚合&lt;/strong&gt;：把文档分组——&lt;code&gt;terms&lt;/code&gt;（按字段值）、&lt;code&gt;date_histogram&lt;/code&gt;（按时间）、&lt;code&gt;range&lt;/code&gt;（按区间）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;度量（Metric）聚合&lt;/strong&gt;：对组内文档算统计——&lt;code&gt;avg/sum/min/max/stats/cardinality&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;71-实例员工数据统计&#34;&gt;7.1 实例：员工数据统计&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;size&amp;#34;&lt;/span&gt;: 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;aggs&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;by_interest&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;# 桶：按兴趣分组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;terms&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;interests&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;aggs&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;avg_age&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;# 嵌套度量：每组平均年龄&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;avg&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;全部员工文档&amp;#34;] --&amp;gt;|&amp;#34;terms 桶 by_interest&amp;#34;| B1[&amp;#34;sports 组&amp;#34;]
    A --&amp;gt; B2[&amp;#34;music 组&amp;#34;]
    A --&amp;gt; B3[&amp;#34;forestry 组&amp;#34;]
    B1 --&amp;gt; C1[&amp;#34;avg_age = 28.5&amp;#34;]
    B2 --&amp;gt; C2[&amp;#34;avg_age = 31.2&amp;#34;]
    B3 --&amp;gt; C3[&amp;#34;avg_age = 35.0&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-要点&#34;&gt;7.2 要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;size: 0&lt;/code&gt;&lt;/strong&gt;：只要聚合不要文档，省掉无用传输；&lt;/li&gt;
&lt;li&gt;聚合只能跑在 &lt;code&gt;keyword&lt;/code&gt;/数值/日期等&lt;strong&gt;精确值字段&lt;/strong&gt;上——在 &lt;code&gt;text&lt;/code&gt; 字段上聚合会失败或慢到崩溃（需要 fielddata，默认关闭，这是个性能地雷）；&lt;/li&gt;
&lt;li&gt;高基数 &lt;code&gt;terms&lt;/code&gt; 聚合（如按 userId 分桶）吃内存，新版有 circuit breaker 兜底；&lt;/li&gt;
&lt;li&gt;近似聚合是 ES 的特色：&lt;code&gt;cardinality&lt;/code&gt;（去重计数）和 &lt;code&gt;percentiles&lt;/code&gt;（百分位）都是&lt;strong&gt;近似算法&lt;/strong&gt;，用微小误差换海量数据下的可用性。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八关系建模es-不是数据库&#34;&gt;八、关系建模：ES 不是数据库&lt;/h2&gt;
&lt;p&gt;书里最&amp;quot;劝退&amp;quot;也最重要的一章：ES 没有 join。建模关系的四种方式按推荐顺序：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方式&lt;/th&gt;
					&lt;th&gt;思路&lt;/th&gt;
					&lt;th&gt;代价&lt;/th&gt;
					&lt;th&gt;适用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;反规范化（Denormalize）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把关联数据冗余进每个文档&lt;/td&gt;
					&lt;td&gt;数据重复、更新要改多处&lt;/td&gt;
					&lt;td&gt;首选，读多写少&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;应用层 join&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;查两次，在代码里拼接&lt;/td&gt;
					&lt;td&gt;多一次网络往返&lt;/td&gt;
					&lt;td&gt;一对多且&amp;quot;多&amp;quot;侧很大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;nested 对象&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;对象数组整体索引，保持内部关联&lt;/td&gt;
					&lt;td&gt;查询稍慢，更新整文档重建&lt;/td&gt;
					&lt;td&gt;对象数组（如文章的评论列表）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;parent-child（join 字段）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;父子文档独立存储，同分片关联&lt;/td&gt;
					&lt;td&gt;查询开销最大&lt;/td&gt;
					&lt;td&gt;子文档频繁独立更新&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;默认答案永远是先反规范化&lt;/strong&gt;。搜索系统的哲学是&amp;quot;用写入时的冗余，换查询时的简单&amp;quot;——磁盘便宜，join 昂贵。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九集群管理与运维要点&#34;&gt;九、集群管理与运维要点&lt;/h2&gt;
&lt;p&gt;最后一部分面向生产：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;节点角色分工&lt;/strong&gt;：master 节点管集群元数据（建议 3 个专职 master 防脑裂）、data 节点存数据、协调节点转发请求。大集群应该角色分离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脑裂与 minimum_master_nodes&lt;/strong&gt;：网络分区时两个子集群各自选主 = 数据写烂。经典配置是 &lt;code&gt;master 节点数 / 2 + 1&lt;/code&gt;（新版本的集群选举机制已自动化）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分片规划&lt;/strong&gt;：分片不是越多越好——每个分片都有固定开销（文件句柄、内存、段管理）。单分片控制在几十 GB 量级、节点堆内存 32GB 以内（压指针上限）、堆内存不超过物理内存一半（另一半留给文件系统缓存）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控集群健康&lt;/strong&gt;：&lt;code&gt;green/yellow/red&lt;/code&gt; + &lt;code&gt;hot_threads&lt;/code&gt; + &lt;code&gt;cat&lt;/code&gt; API 家族。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;升级与备份&lt;/strong&gt;：滚动升级逐个节点来；快照（snapshot）到 S3/HDFS 做备份。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十全书知识地图&#34;&gt;十、全书知识地图&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    CORE[&amp;#34;倒排索引&amp;lt;br/&amp;gt;（一切的根基）&amp;#34;]
    CORE --&amp;gt; WRITE[&amp;#34;写入链路&amp;lt;br/&amp;gt;refresh/translog/merge&amp;lt;br/&amp;gt;→ 近实时 + 不丢数据&amp;#34;]
    CORE --&amp;gt; SEARCH[&amp;#34;搜索&amp;lt;br/&amp;gt;Query/Filter + bool 组合&amp;lt;br/&amp;gt;TF-IDF 相关性算分&amp;#34;]
    CORE --&amp;gt; AGG[&amp;#34;聚合&amp;lt;br/&amp;gt;桶 × 度量嵌套&amp;#34;]
    WRITE --&amp;gt; DIST[&amp;#34;分布式&amp;lt;br/&amp;gt;分片路由 scatter-gather&amp;lt;br/&amp;gt;主副本容灾&amp;#34;]
    SEARCH --&amp;gt; LANG[&amp;#34;语言处理&amp;lt;br/&amp;gt;分析器/纠错/输入即搜&amp;#34;]
    SEARCH --&amp;gt; MODEL[&amp;#34;建模&amp;lt;br/&amp;gt;反规范化优先&amp;#34;]
    DIST --&amp;gt; OPS[&amp;#34;运维&amp;lt;br/&amp;gt;健康监控/分片规划/防脑裂&amp;#34;]
    style CORE fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;如果只能记住三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一切性能与行为的答案都在倒排索引里&lt;/strong&gt;：不可变段决定了 NRT，分词决定了查不查得到，keyword/text 之分决定了能不能聚合；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;query 算分、filter 缓存&lt;/strong&gt;——写任何查询前先想这一条；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分片是分布式的原子&lt;/strong&gt;：路由公式决定了主分片数不能改，副本决定了读吞吐和容灾。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：Clinton Gormley, Zachary Tong. 《Elasticsearch: The Definitive Guide》. O&amp;rsquo;Reilly, 2015. 中文版：&lt;a href=&#34;https://github.com/looly/elasticsearch-definitive-guide-cn&#34;&gt;elasticsearch-definitive-guide-cn&lt;/a&gt;（looly 等译）。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
