Meilisearch 与轻量搜索替代方案——当 ES 太重时 / Meilisearch and Lightweight Search Alternatives
📅 创建时间:2026-07-28 🏷️ 标签:#Meilisearch #Typesense #OpenSearch #PostgreSQL #全文搜索 #选型 #轻量搜索 📚 前置知识:[[./00-overview]](搜索引擎概览) [[./02-elasticsearch-queries]](ES 查询 DSL)
📋 本章目标
- 理解 Meilisearch 的核心特性及其与 ES 的本质差异
- 掌握 Meilisearch 的部署、索引配置和搜索 API
- 能够对比 Meilisearch、Typesense、OpenSearch 的功能矩阵
- 理解 PostgreSQL 全文搜索的适用场景与局限性
- 掌握搜索引擎选型的决策树和优先级排序
- 能够在"轻量体验"和"无限控制"之间做出正确取舍
第1部分:Meilisearch 核心特性
1.1 Meilisearch 是什么
Meilisearch 是一个用 Rust 编写的开源搜索引擎,核心理念是"开箱即用、零配置、即时搜索"。它的定位非常清晰:做 ES 能做但太复杂的那部分事情。
┌─────────────────────────────────────────────────────────────┐
│ Meilisearch 定位 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Meilisearch = 一个二进制文件,一条命令启动 │
│ │
│ $ ./meilisearch │
│ → Server is listening on http://127.0.0.1:7700 │
│ │
│ 对比 ES: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Meilisearch ES │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ 启动方式 一条命令 JVM配置+配置 │ │
│ │ 配置文件 无(可选) elasticsearch.yml │ │
│ │ JVM 调优 不需要(Rust) Heap + GC 调优 │ │
│ │ 中文分词 内置(Jieba) 需要安装插件 │ │
│ │ 同义词管理 REST API 配置文件或API │ │
│ │ 排序规则 内置规则引擎 自定义 function_score │ │
│ │ 容错/拼写纠错 默认开启 需要 fuzzy query │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.2 即时搜索(Search-as-you-type)
Meilisearch 的招牌能力是"即时搜索"——用户每输入一个字符,结果立即更新。这不是简单的 prefix 查询,而是多层次的匹配策略。
┌─────────────────────────────────────────────────────────────┐
│ Meilisearch 即时搜索机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户输入: "app" │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Meilisearch 的匹配策略(按优先级排序): │ │
│ │ │ │
│ │ 1. Typo Tolerance(容错匹配,默认开启) │ │
│ │ "app" → 匹配 "apple", "apply", "appendix" │ │
│ │ 允许 1-2 个字符的编辑距离差异 │ │
│ │ │ │
│ │ 2. Prefix Search(前缀匹配) │ │
│ │ "app" → 匹配 "apple", "application" │ │
│ │ 所有以 "app" 开头的词 │ │
│ │ │ │
│ │ 3. 分词匹配 │ │
│ │ "app" → 匹配 "App Store", "web app" │ │
│ │ 当 "app" 作为独立词出现时 │ │
│ │ │ │
│ │ 4. 排序规则(Relevancy Rules) │ │
│ │ • 精确匹配 > 前缀匹配 > 容错匹配 │ │
│ │ • 短字段匹配 > 长字段匹配 │ │
│ │ • 词序匹配 > 乱序匹配 │ │
│ │ • 标题匹配 > 正文匹配 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 与此对比,ES 需要手动配置: │
│ - ngram tokenizer / edge ngram │
│ - fuzzy query │
│ - multi_match with type and boost │
│ │
└─────────────────────────────────────────────────────────────┘1.3 容错与同义词
┌─────────────────────────────────────────────────────────────┐
│ 容错与同义词管理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 容错(Typo Tolerance)—— 默认开启,无需配置 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Meilisearch 的容错规则: │ │
│ │ │ │
│ │ 词长度 允许的编辑距离 举例 │ │
│ │ ─────────────────────────────────────────── │ │
│ │ 1-4 0(不纠错) "the" 不变 │ │
│ │ 5-8 1 "aple" → "apple" │ │
│ │ >8 2 "elastcsear" → │ │
│ │ "elasticsearch" │ │
│ │ │ │
│ │ 编辑距离 = Levenshtein distance │ │
│ │ (一个字符的增/删/改/换位 = 1 次操作) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 同义词 —— 通过 REST API 动态管理 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /indexes/products/settings/synonyms │ │
│ │ { │ │
│ │ "phone": ["mobile", "cellphone", "smartphone"], │ │
│ │ "laptop": ["notebook", "macbook"] │ │
│ │ } │ │
│ │ │ │
│ │ → 搜索 "mobile" 会自动匹配 "phone" 的文档 │ │
│ │ → 同义词是单向还是双向?→ 单向 (搜索词映射) │ │
│ │ → 不需要修改索引或重建,即时生效! │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 排序规则(Ranking Rules)—— 内置可覆盖 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 默认排序规则(优先级从高到低): │ │
│ │ │ │
│ │ 1. words: 匹配词数(越多越好) │ │
│ │ 2. typo: 无容错 > 有容错(精确优先) │ │
│ │ 3. proximity: 词距离(越近越好) │ │
│ │ 4. attribute: 字段重要性(标题 > 正文) │ │
│ │ 5. wordsPosition: 词位置(越靠前越好) │ │
│ │ 6. exactness: 完全匹配 > 部分匹配 │ │
│ │ │ │
│ │ 可自定义添加规则: │ │
│ │ ["words","typo","proximity","price:asc", │ │
│ │ "release_date:desc","exactness"] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.4 Meilisearch 实战:文档搜索
┌─────────────────────────────────────────────────────────────┐
│ Meilisearch 完整搜索流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ # 1. 启动(Docker) │
│ docker run -p 7700:7700 getmeili/meilisearch:latest \ │
│ meilisearch --master-key="masterKey" │
│ │
│ # 2. 创建索引 + 配置可搜索属性 │
│ POST /indexes │ │
│ {"uid": "movies", "primaryKey": "id"} │
│ │
│ PUT /indexes/movies/settings │ │
│ { │
│ "searchableAttributes": ["title", "overview", "genres"], │
│ "filterableAttributes": ["release_date", "rating", │
│ "genres"], │
│ "sortableAttributes": ["rating", "release_date"], │
│ "rankingRules": ["words","typo","proximity", │
│ "rating:desc","release_date:desc", │
│ "exactness"] │
│ } │
│ │
│ # 3. 添加文档 │
│ POST /indexes/movies/documents │ │
│ [{"id":1,"title":"Inception","overview":"A thief...", │
│ "genres":["Sci-Fi","Thriller"],"rating":8.8}] │
│ │
│ # 4. 搜索 │
│ POST /indexes/movies/search │ │
│ { │
│ "q": "incepction sci-fi", // 故意拼错 "inception" │
│ "filter": "rating >= 7.0 AND release_date > 2010-01-01",│
│ "sort": ["rating:desc"], │
│ "limit": 20 │
│ } │
│ // → 返回 "Inception",拼写自动纠正! │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:Meilisearch vs Elasticsearch 深度对比
2.1 部署复杂度
┌─────────────────────────────────────────────────────────────┐
│ 部署复杂度对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Meilisearch 部署: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Docker: │ │
│ │ docker run -p 7700:7700 -v $(pwd)/data:/data │ │
│ │ getmeili/meilisearch:latest │ │
│ │ │ │
│ │ 就这么一行。没有集群概念,没有分片,没有 node。 │ │
│ │ 一个进程 = 一个搜索引擎。 │ │
│ │ │ │
│ │ HA 方案:多实例 + 应用层负载均衡 + 独立索引 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Elasticsearch 部署: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 配置 JVM Heap(-Xms31g -Xmx31g) │ │
│ │ 2. 配置 elasticsearch.yml(cluster.name, node.name,│ │
│ │ discovery.seed_hosts, network.host, ...) │ │
│ │ 3. 配置系统参数(vm.max_map_count, 文件描述符, │ │
│ │ swap 禁用, ...) │ │
│ │ 4. 安装分词器插件(ik, pinyin...) │ │
│ │ 5. 配置 TLS/认证(7.x+ 内置安全功能) │ │
│ │ 6. 配置 Kibana (可选) │ │
│ │ 7. 3 个节点 × 以上步骤 │ │
│ │ │ │
│ │ 至少半个工作日。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 功能对比矩阵
┌─────────────────────────────────────────────────────────────┐
│ Meilisearch vs ES 功能深度对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 功能 Meilisearch ES │
│ ──────────────────────────────────────────────────────── │
│ 全文搜索 ✅ 优秀 ✅ 优秀 │
│ 即时搜索/前缀匹配 ✅ 开箱即用 ⚠️ 需配置 │
│ 拼写纠错 ✅ 默认开启 ⚠️ fuzzy │
│ 同义词管理 ✅ REST API ✅ API/文件 │
│ 自定义排序规则 ✅ 内置引擎 ✅ 灵活 │
│ 多语言支持 ✅ 内置多语言 ⚠️ 需插件 │
│ Facet / Filter ✅ 简单 ✅ 强大 │
│ Aggregations ⚠️ 仅 facet ✅ 极强 │
│ 地理位置搜索 ✅ 内置 ✅ 内置 │
│ 向量搜索 / kNN ❌ 实验性 ✅ 成熟 │
│ SQL 查询 ❌ ✅ │
│ 数据分析/Aggregation Pipeline ❌ ✅ │
│ Logstash/Beats 集成 ❌ ✅ │
│ Kibana 可视化 ❌ ✅ │
│ 集群/分片/副本 ❌ (单进程) ✅ │
│ RBAC/多租户 ⚠️ 基础 ✅ 企业级 │
│ Snapshot/Restore ⚠️ Dump 文件 ✅ 增量快照 │
│ Script / Painless ❌ ✅ │
│ Ingest Pipeline ❌ ✅ │
│ Index Lifecycle Management ❌ ✅ │
│ Rollover / Rollup ❌ ✅ │
│ │
│ 一句话总结: │
│ Meilisearch = 搜索体验的最佳化 │
│ ES = 数据平台的全能化 │
│ │
└─────────────────────────────────────────────────────────────┘2.3 性能特征
┌─────────────────────────────────────────────────────────────┐
│ 性能特征对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 维度 Meilisearch ES │
│ ──────────────────────────────────────────────────────── │
│ 搜索延迟 (P50) <10ms <50ms │
│ 搜索延迟 (P99) <50ms <200ms │
│ 写入吞吐 中等 (~5k/s) 高 (~50k/s 集群) │
│ 数据容量上限 数十GB-数百GB TB-PB 级 │
│ 内存占用 (空载) ~100MB ~1GB (JVM基础) │
│ 启动时间 秒级 十秒-分钟级 │
│ 并发查询 单进程(可横向扩展) 集群原生支持 │
│ │
│ 延迟更低的秘密: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Meilisearch: │ │
│ │ • Rust 编写(零 GC、内存安全、SIMD 优化) │ │
│ │ • LMDB (Memory-Mapped DB) 作为存储引擎 │ │
│ │ • 所有索引数据内存映射,无 JVM 开销 │ │
│ │ │ │
│ │ ES: │ │
│ │ • JVM + Lucene(功能丰富但开销更大) │ │
│ │ • 分布式架构带来额外的网络开销 │ │
│ │ • GC 停顿可能导致 P99 延迟抖动 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:Typesense
3.1 Typesense 概览
┌─────────────────────────────────────────────────────────────┐
│ Typesense 定位 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Typesense 是 Meilisearch 最接近的竞争对手,同样定位于 │
│ "轻量、快速、开箱即用的即时搜索引擎"。 │
│ │
│ 核心差异: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Typesense Meilisearch │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ 编写语言 C++ Rust │ │
│ │ 许可证 GPLv3 MIT │ │
│ │ 集群支持 原生支持 ❌ (单进程) │ │
│ │ API 类 ES (类似) 自研 REST API │ │
│ │ 自定义排序 排序规则 Ranking Rules │ │
│ │ 联合搜索 ✅ (Multi-Search) ❌ │ │
│ │ 向量搜索 ❌ (实验性) ❌ (实验性) │ │
│ │ Cloud 服务 有 (Typesense Cloud) 有 (Meili Cloud) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Typesense 的优势: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 原生集群支持(Raft 共识协议) │ │
│ │ → 高可用不需要外部负载均衡器 │ │
│ │ │ │
│ │ 2. C++ 编写,性能接近裸金属 │ │
│ │ → 单节点可处理 100+ 并发搜索 │ │
│ │ │ │
│ │ 3. Class ES-like API │ │
│ │ → 从 ES 迁移的学习成本低 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.2 Typesense 搜索实战
┌─────────────────────────────────────────────────────────────┐
│ Typesense 搜索示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ # 创建 Collection (类比 ES Index) │
│ POST /collections │
│ { │
│ "name": "products", │
│ "fields": [ │
│ {"name": "title", "type": "string"}, │
│ {"name": "price", "type": "float", "facet": true}, │
│ {"name": "brand", "type": "string", "facet": true}, │
│ {"name": "rating", "type": "int32"} │
│ ], │
│ "default_sorting_field": "rating" │
│ } │
│ │
│ # 搜索 │
│ GET /collections/products/documents/search │
│ ?q=小米手机 │
│ &query_by=title,description │
│ &filter_by=price:<5000 && brand:=[小米,华为] │
│ &sort_by=rating:desc │
│ &facet_by=brand,price │
│ &num_typos=2 // 允许 2 个拼写错误 │
│ &prefix=true // 前缀匹配 │
│ │
│ # 响应 │
│ { │
│ "found": 150, │
│ "facet_counts": [ │
│ {"field_name": "brand", │
│ "counts": [{"value":"小米","count":80}, │
│ {"value":"华为","count":70}]} │
│ ], │
│ "hits": [...] │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘3.3 Typesense 集群架构
┌─────────────────────────────────────────────────────────────┐
│ Typesense 集群拓扑 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Typesense 使用 Raft 共识协议实现原生集群,无需外部 ZooKeeper│
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Typesense Cluster │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │
│ │ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ │
│ │ │ (Raft │ │ (Raft │ │ (Raft │ │ │
│ │ │ Leader) │ │ Follower) │ │ Follower)│ │ │
│ │ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌───────┐ │ │ │
│ │ │ │Data │ │ │ │Data │ │ │ │Data │ │ │ │
│ │ │ │Node 0 │ │ │ │Node 1 │ │ │ │Node 2 │ │ │ │
│ │ │ └────────┘ │ │ └────────┘ │ │ └───────┘ │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────┘ │ │
│ │ │ │
│ │ 写请求 → 总是发到 Raft Leader │ │
│ │ 读请求 → 可发到任意节点(最终一致性) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Typesense 的节点既是搜索节点又是数据分片, │
│ 比 ES 的架构简单得多(不需要区分 Master/Data/Coordinating)│
│ │
└─────────────────────────────────────────────────────────────┘第4部分:OpenSearch
4.1 OpenSearch 的由来
┌─────────────────────────────────────────────────────────────┐
│ Elasticsearch → OpenSearch 分支史 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 时间线: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 2010: Elasticsearch 诞生 (Shay Banon) │ │
│ │ 2012: Elastic 公司成立 │ │
│ │ 2018: ES 默认发行版中加入 X-Pack (部分收费) │ │
│ │ 2021.01: ES 7.11 → License 从 Apache 2.0 改为 │ │
│ │ SSPL + Elastic License (非 OSI 开源) │ │
│ │ → AWS 认为这违反了开源精神 │ │
│ │ │ │
│ │ 2021.01: AWS Fork ES 7.10.2 → OpenSearch 1.0 │ │
│ │ + OpenSearch Dashboards (Fork Kibana) │ │
│ │ 许可证:Apache 2.0 │ │
│ │ │ │
│ │ 2024: OpenSearch 2.x+ 支持向量搜索、ML插件 │ │
│ │ ES 8.x+ 同样支持,但两个项目已明显分化 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 当前状态:ES 和 OpenSearch 是两个独立竞争的项目, │
│ API 基本兼容但功能方向已分化。 │
│ │
└─────────────────────────────────────────────────────────────┘4.2 OpenSearch vs ES 功能对比
┌─────────────────────────────────────────────────────────────┐
│ OpenSearch vs ES 核心差异 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 维度 OpenSearch ES │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ 许可证 Apache 2.0 SSPL/EL │ │
│ │ AWS 集成 ✅ 原生 ⚠️ 需配置 │ │
│ │ 安全功能 免费内置 免费基础版 │ │
│ │ (RBAC/LDAP/SAML) 高级功能收费 │ │
│ │ Alerting/通知 ✅ 免费 ✅ 收费 │ │
│ │ Anomaly Detection ✅ 免费 (ML) ✅ 收费 │ │
│ │ SQL 支持 ✅ 内置 ✅ 内置 │ │
│ │ 向量搜索 ✅ 2.x+ ✅ 8.x+ │ │
│ │ kNN 算法 FAISS / NMSLIB Lucene HNSW │ │
│ │ 可观测性插件 独立项目 收费/基础 │ │
│ │ 社区支持 AWS + 社区 Elastic 公司 │ │
│ │ 版本更新速度 快 (AWS 驱动) 快 (Elastic) │ │
│ │ 与 Kibana 兼容 ❌(用 Dashboards) ✅ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 选 OpenSearch 的理由: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 合规要求:Apache 2.0 许可,无法律风险 │ │
│ │ 2. AWS 生态:CloudFormation, IAM, VPC 深度集成 │ │
│ │ 3. 成本:安全+告警+异常检测全部免费 │ │
│ │ 4. 不信任 Elastic 的许可变更历史 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 选 ES 的理由: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 更大的社区和生态(Kibana, Logstash, Beats) │ │
│ │ 2. 更成熟的功能和更多的文档/教程 │ │
│ │ 3. Elastic Cloud 托管服务体验更好 │ │
│ │ 4. 创新速度更快(Elastic 公司有全职工程师) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第5部分:PostgreSQL 全文搜索
5.1 何时该用 PG 全文搜索
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL 全文搜索适用场景 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景特征: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ✅ 数据已经在 PostgreSQL 中 │ │
│ │ ✅ 搜索是辅助功能(不是核心产品) │ │
│ │ ✅ 数据量 < 100 万条 │ │
│ │ ✅ 不需要聚合分析 │ │
│ │ ✅ 不需要即时搜索/拼写纠错 │ │
│ │ ✅ 团队没有运维 ES 的人 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 场景特征反面: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ❌ 搜索是核心功能(如电商搜索) │ │
│ │ ❌ 数据量 > 百万级 │ │
│ │ ❌ 需要 Facet/聚合分析 │ │
│ │ ❌ 需要高并发(PG 全文搜索无分布式能力) │ │
│ │ ❌ 需要拼音搜索、拼写纠错、同义词等高级功能 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 PG 全文搜索核心语法
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL 全文搜索实战 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心类型: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ tsvector: 分词后的文档(类似 ES 的倒排索引) │ │
│ │ tsquery: 分词后的查询(类似 ES 的查询 AST) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 基础用法: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ -- 将文本转为 tsvector │ │
│ │ SELECT to_tsvector('english', │ │
│ │ 'The quick brown fox jumps'); │ │
│ │ → 'brown':3 'fox':4 'jump':5 'quick':2 │ │
│ │ (去掉了停用词 'the', 'jumps' 词干化为 'jump') │ │
│ │ │ │
│ │ -- 将查询转为 tsquery │ │
│ │ SELECT to_tsquery('english', 'jumping & fox'); │ │
│ │ → 'jump' & 'fox' │ │
│ │ │ │
│ │ -- 搜索 │ │
│ │ SELECT * FROM documents │ │
│ │ WHERE tsv_content @@ to_tsquery('english', │ │
│ │ 'jumping & fox'); │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 生产级方案:GIN 索引 + 生成列 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ -- 添加 tsvector 生成列 │ │
│ │ ALTER TABLE products ADD COLUMN tsv tsvector │ │
│ │ GENERATED ALWAYS AS ( │ │
│ │ setweight(to_tsvector('simple', │ │
│ │ coalesce(title,'')), 'A') || │ │
│ │ setweight(to_tsvector('simple', │ │
│ │ coalesce(description,'')), 'B') │ │
│ │ ) STORED; │ │
│ │ │ │
│ │ -- 创建 GIN 索引 │ │
│ │ CREATE INDEX idx_products_tsv │ │
│ │ ON products USING GIN (tsv); │ │
│ │ │ │
│ │ -- setweight: 'A' 权重 > 'B' 权重 │ │
│ │ -- 意味着标题匹配比描述匹配更重要 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 查询语法速查: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 'A & B' → AND(两个词都必须有) │ │
│ │ 'A | B' → OR │ │
│ │ '!A' → NOT │ │
│ │ 'A <-> B'→ FOLLOWED BY(A 紧接着 B) │ │
│ │ 'A <2> B'→ 距离不超过 2 个位置 │ │
│ │ 'A:*' → 前缀匹配 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.3 PG 全文搜索的局限性
┌─────────────────────────────────────────────────────────────┐
│ PG 全文搜索与专用搜索引擎的差距 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 没有倒排索引的跳表/BitSet 缓存 │ │
│ │ → 复杂多词查询性能不如 Lucene │ │
│ │ │ │
│ │ 2. 没有 field data cache │ │
│ │ → 排序和聚合依赖 B-Tree,而不是列式缓存 │ │
│ │ │ │
│ │ 3. 没有 BM25(PG 使用 tf-idf 变体) │ │
│ │ → 相关性排序质量稍逊 │ │
│ │ → PG 11+ 支持 ts_rank_cd(覆盖密度排序) │ │
│ │ │ │
│ │ 4. 没有分布式能力 │ │
│ │ → 单机处理,数据量受限于单机资源 │ │
│ │ → 没有分片、副本、负载均衡 │ │
│ │ │ │
│ │ 5. 缺少用户体验功能 │ │
│ │ → 无拼写纠错、无同义词引擎、无自动补全 │ │
│ │ → 需要应用层自己实现(pg_trgm 可部分弥补) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 但仍然是一个不错的选择,如果: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 搜索不是核心功能 │ │
│ │ • 数据量可控(百万以内) │ │
│ │ • 不想引入新的基础设施 │ │
│ │ • 只需要基本的全文检索 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第6部分:选型决策树
6.1 搜索引擎选型决策流程
┌─────────────────────────────────────────────────────────────┐
│ 搜索引擎选型决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 你的数据量多大? │
│ ├── < 100 万条,且数据已在 PG 中 │
│ │ ├── 需要 Facet/聚合? │
│ │ │ ├── 需要 → ES / OpenSearch │
│ │ │ └── 不需要 → PostgreSQL 全文搜索 👍 │
│ │ └── 需要自动补全/纠错? │
│ │ ├── 需要 → Meilisearch / Typesense 👍 │
│ │ └── 不需要 → PostgreSQL 全文搜索 👍 │
│ │ │
│ ├── 100 万 - 1000 万条 │
│ │ ├── 搜索是核心功能? │
│ │ │ ├── 是 → ES / OpenSearch │
│ │ │ └── 否 → Meilisearch / Typesense 👍 │
│ │ ├── 需要日志分析 (ELK Stack)? │
│ │ │ └── 是 → ES(别无选择) │
│ │ └── 需要向量搜索 (RAG)? │
│ │ └── 是 → ES / OpenSearch │
│ │ │
│ └── > 1000 万条 │
│ ├── AWS 云环境?→ OpenSearch 👍 │
│ ├── 许可证敏感?→ OpenSearch 👍 │
│ └── 其他 → ES │
│ │
│ 额外维度: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 运维能力: │ │
│ │ • 1 人团队 → Meilisearch / PG │ │
│ │ • 有 DevOps → Typesense / ES / OpenSearch │ │
│ │ • 有专职 SRE → ES / OpenSearch (发挥全部潜力) │ │
│ │ │ │
│ │ 预算: │ │
│ │ • $0 → Meilisearch / PG / OpenSearch │ │
│ │ • 有预算 → ES Cloud / Typesense Cloud │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.2 决策优先级排序
┌─────────────────────────────────────────────────────────────┐
│ 选型决策优先级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第1优先级:功能需求(缺了没法用) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 需要聚合分析?→ 排除 Meilisearch / Typesense │ │
│ │ • 需要日志分析?→ 只能用 ES │ │
│ │ • 需要向量搜索?→ 排除 Meilisearch / Typesense │ │
│ │ • 需要 SQL 查询?→ 排除 Meilisearch / Typesense │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第2优先级:运维约束(用不了也没办法) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 运维人员 ≤ 1 人?→ Meilisearch / PG │ │
│ │ • 必须 Apache 2.0?→ OpenSearch / Meilisearch │ │
│ │ • 数据安全是软性需求?→ Meilisearch / Typesense │ │
│ │ • 数据安全是硬性需求?→ ES / OpenSearch │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第3优先级:体验与生态(锦上添花) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 中文支持开箱即用?→ Meilisearch / Typesense │ │
│ │ • 前端 SDK 丰富?→ Meilisearch (instant-meilisearch)│ │
│ │ • 社区生态成熟?→ ES │ │
│ │ • 托管服务便宜?→ Typesense Cloud │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.3 迁移成本矩阵
┌─────────────────────────────────────────────────────────────┐
│ 从不同方案迁移的代价 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 从 A → B 的迁移成本估算: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 迁出 \ 迁入 ES OpenSearch Meili Typesense│
│ │ ───────────────────────────────────────────────── │ │
│ │ PG 全文搜索 中 (重建) 中 (重建) 低 (API) 低 │ │
│ │ ES (自建) - 低 (兼容) 高 (重写) 高 │ │
│ │ OpenSearch 低 (兼容) - 高 (重写) 高 │ │
│ │ Meilisearch 高 (重写) 高 (重写) - 中 │ │
│ │ Typesense 高 (重写) 高 (重写) 中 - │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 从 PostgreSQL 迁出到专用搜索引擎是最常见的升级路径: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 设计新引擎的 Mapping / Schema │ │
│ │ 2. 编写数据同步脚本 (PG → 搜索引擎) │ │
│ │ 3. 并行运行:应用同时查 PG 和新引擎 │ │
│ │ 4. A/B 测试:对比搜索结果质量 │ │
│ │ 5. 灰度切流:10% → 50% → 100% │ │
│ │ 6. 下线 PG 搜索(保留 PG 作主数据库) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:四大引擎定位口诀
ES: 功能最多、运维最重、选它最多
OpenSearch: ES 的开源替代品(Apache 2.0)、AWS 原生
Meilisearch: 搜索体验最好、部署最简单、集群缺失
Typesense: 轻量+集群、Meilisearch 最强竞品
PG 全文搜索: 最简单、数据在哪搜哪、百万级适用总结2:选型速查
| 条件 | 首选 | 备选 |
|---|---|---|
| 电商搜索(聚合+排序) | ES | OpenSearch |
| 日志分析 (ELK) | ES | - |
| 文档/内容站内搜索 | Meilisearch | Typesense |
| SaaS 内置搜索 | Typesense | Meilisearch |
| 小项目(已有 PG) | PG 全文搜索 | Meilisearch |
| 合规 (Apache 2.0) | OpenSearch | Meilisearch |
| RAG 向量搜索 | ES / OpenSearch | - |
总结3:三个决策问题,找到你的引擎
Q1: 是否需要聚合/分析?
Yes → ES / OpenSearch
No → Q2
Q2: 是否需要集群/分布式?
Yes → Typesense / ES / OpenSearch
No → Q3
Q3: 追求开发体验还是功能深度?
体验 → Meilisearch
深度 → ES / OpenSearch总结4:PG 全文搜索的边界
优势:零额外运维、数据一致性天然保证、GIN 索引高效
劣势:无分布式能力、无高级搜索功能(拼写纠错、同义词引擎)
边界:百万级数据、搜索非核心功能章节测试
测试1:Meilisearch
Meilisearch 的即时搜索(Search-as-you-type)依赖哪些内置机制?如果要用 ES 实现同样的效果,需要配置哪些东西?
测试2:性能差异
为什么 Meilisearch 的搜索延迟通常低于 ES?从技术栈角度解释。
测试3:集群
Typesense 和 Meilisearch 在集群支持上的核心差异是什么?这如何影响生产环境部署?
测试4:OpenSearch
如果公司要求所有服务器软件必须是 Apache 2.0 许可,你会选择 ES 还是 OpenSearch?为什么?
测试5:PG 全文搜索
PostgreSQL 的 tsvector + GIN 索引方案,在什么数据量级下性能会明显下降?为什么?
测试6:选型
你是初创公司唯一的后端工程师。产品需要给一个文档管理 SaaS 添加搜索功能,数据量预计 50 万文档。你会选什么方案?说明理由。
测试7:综合
一个中型电商团队(5 名后端,有 DevOps),搜索数据 500 万商品,需要:关键词搜索、品牌/价格 Facet、按销量排序、自动补全。预算充足。请给出搜索引擎选型和关键架构设计。
参考答案
测试1答案
答案: Meilisearch 内置:Typo Tolerance(容错匹配)、Prefix Search(前缀匹配)、分词匹配、Ranking Rules(排序规则优先级)。这些都是默认开启的,不需要配置。
ES 实现同样效果需要:
- 配置 edge_ngram 或 completion suggester(前缀匹配)
- 使用 fuzzy query(拼写纠错)
- 配置 multi_match + boosting(字段权重)
- 使用 function_score(排序规则)
- 自行实现搜索 UX 的 debounce 和结果更新逻辑
测试2答案
答案:
- Meilisearch 用 Rust 编写,零垃圾回收,无 GC 停顿;ES 在 JVM 上运行,GC 会导致 P99 延迟抖动
- Meilisearch 使用 LMDB(Memory-Mapped DB),数据直接映射到虚拟内存,OS 管理缓存;ES 通过 JVM Heap + OS Page Cache 两层
- Meilisearch 是单进程架构,无网络往返开销;ES 需要协调节点聚合各分片结果
测试3答案
答案: Typesense 原生支持集群(基于 Raft 共识协议),多节点组成一个有 Leader/Follower 关系的集群,自带高可用和故障切换。
Meilisearch 是单进程架构,没有集群概念。高可用需要外部方案:多个 Meilisearch 实例 + 应用层负载均衡 + 各实例独立维护索引(数据一致性由应用层保证)。
影响:生产环境中,Typesense 可以直接以 3 节点集群部署(类似 ES 的最小部署),Meilisearch 要么接受单点故障,要么自己搭建多实例负载均衡。
测试4答案
答案:选择 OpenSearch。因为 ES 从 7.11 版本开始,默认发行版的许可证已从 Apache 2.0 改为 SSPL + Elastic License,两者都不是 OSI 批准的开源许可证。Apache 2.0 对商业使用几乎没有限制,而 SSPL 要求如果你将软件作为服务提供,必须开源整个相关栈的代码(对 SaaS 公司有法律风险)。OpenSearch 作为 ES 7.10.2 的 Apache 2.0 fork,功能兼容且许可证友好。
测试5答案
答案:一般在百万级(百万到几百万条)文档时,PG 全文搜索性能开始明显下降。原因:
- GIN 索引本身可以支持到千万级,但搜索如果涉及复杂查询(多词 AND/OR + 距离 + 排序),单机的 CPU/内存会成为瓶颈
- PG 缺乏专用的 field data cache 和 filter cache 机制,排序和过滤严重依赖磁盘 IO
- 没有分布式能力,无法利用多节点并行查询
如需突破,可以考虑:只索引必要字段、分区表 + 并行查询、读写分离、或升级为专用搜索引擎。
测试6答案
答案:推荐 Meilisearch。理由:
- 50 万文档在单节点 Meilisearch 上表现极好
- 文档搜索需要即时搜索 + 拼写纠错,Meilisearch 开箱即用
- 唯一后端工程师 → 部署和运维成本最低(一条 Docker 命令)
- 不需要 Facet/聚合分析 → ES 的复杂度是多余的
- MIT 许可证 → SaaS 使用无法律风险
测试7答案
答案:推荐 Elasticsearch。关键架构设计:
- 集群规模:3 Master 节点 + 5 Data 节点(每节点 4 核 32GB RAM)
- 分片策略:6-8 个主分片,1 个副本(每分片约 30GB)
- 同步方案:CDC (Canal → Kafka → ES Sink Connector),近实时同步
- 搜索架构:
- 关键词搜索:multi_match (best_fields) with 字段权重
- Facet 聚合:terms + range aggregation
- 自动补全:completion suggester (独立字段)
- 排序:_score + function_score (销量因子)
- 备选:如果团队对 AWS 有倾向,OpenSearch 也完全可以胜任
相关笔记
- [[./00-overview]] - 搜索引擎概览
- [[./02-elasticsearch-queries]] - ES 查询 DSL 深入
- [[./03-elasticsearch-cluster]] - ES 集群规划与运维
- [[/03-web/06-databases-and-data-access/09-olap-databases]] - 数据分析引擎对比
下一步学习
- [ ] 实践:搭建一个 Meilisearch Docker 实例,导入自己的项目数据
- [ ] 实践:对比同一个查询在 ES 和 Meilisearch 上的延迟差异
- [ ] 阅读 04-ai 向量搜索与 RAG 相关内容(了解搜索的下一阶段:语义搜索与向量检索)
学习状态:🟡 开始学习