返回博客

Apache Doris 深度实践(下):向量索引+倒排索引

在 coomia-dip 之前的架构设计中,我们最初考虑了典型的多引擎方案:

Coomia发布于 2025年11月10日19 分钟阅读
分享本文Twitter / X

系列:S8 技术组件深潜 · 第 2 篇 | 难度:高级 | 阅读时间:20 分钟

Apache Doris 深度实践(下):向量索引+倒排索引

#TL;DR

  • Doris 2.1+ 原生支持 HNSW 向量索引和倒排索引,使得 coomia-dip 无需部署独立的 Milvus/Elasticsearch 即可实现语义搜索和全文检索
  • 通过倒排索引实现亚秒级全文搜索,替代了传统 Elasticsearch 方案,在 5000 万条 Ontology 描述文档上搜索延迟 < 300ms
  • 向量索引 + 倒排索引 + OLAP 分析的混合查询是 coomia-dip 的核心差异化能力,本文详解其配置、调优和生产实践

#1. 统一引擎的价值

#1.1 传统方案的痛点

在 coomia-dip 之前的架构设计中,我们最初考虑了典型的多引擎方案:

Code
OLAP 分析     → ClickHouse / Doris
全文检索       → Elasticsearch
向量语义搜索   → Milvus / Pinecone

这种方案有三个核心痛点:

  1. 数据一致性:同一份数据需要同步到三个引擎,延迟和一致性难以保证
  2. 运维复杂度:三套集群的部署、监控、升级、备份
  3. 查询割裂:混合查询(如"找到与某向量相似且包含特定关键词的最近30天数据")需要应用层做多次查询和结果合并

#1.2 Doris 2.1+ 的统一能力

Doris 2.1 引入了向量索引和倒排索引,使得单引擎可以同时满足三种需求:

能力实现方式性能指标
OLAP 分析列存 + 向量化执行 + MPP亚秒级聚合
全文检索倒排索引 (基于 CLucene)< 300ms
向量检索HNSW 索引< 200ms (Top-100)

#1.3 在 coomia-dip 中的应用场景

场景使用索引示例
Ontology 对象搜索倒排索引搜索名称或描述包含"供应链"的所有对象
语义相似对象发现向量索引找到与"供应链风险评估"语义最相似的 Action
混合智能搜索倒排 + 向量 + OLAP找到最近7天创建的、与用户查询语义相似且名称匹配的对象
知识库问答向量索引RAG 场景下检索最相关的知识片段
异常检测辅助向量索引找到与异常样本 embedding 最相似的历史记录

#2. 倒排索引深度解析

#2.1 倒排索引架构

Doris 的倒排索引基于 CLucene(Lucene 的 C++ 实现)构建,与列存数据紧密集成:

Code
┌──────────────────────────────────────┐
│            Doris Segment             │
│  ┌─────────────┬──────────────────┐  │
│  │ Column Data  │ Inverted Index   │  │
│  │ (Columnar)   │ (CLucene)        │  │
│  │              │ ┌──────────────┐ │  │
│  │ tenant_id    │ │ Term Dict    │ │  │
│  │ object_type  │ │ Posting List │ │  │
│  │ display_name │ │ Doc Values   │ │  │
│  │ description  │ │ Norms        │ │  │
│  │ properties   │ └──────────────┘ │  │
│  └─────────────┴──────────────────┘  │
└──────────────────────────────────────┘

#2.2 创建倒排索引

coomia-dip 中 Ontology 对象表的倒排索引配置:

SQL
CREATE TABLE ontology_object_searchable (
    tenant_id      VARCHAR(64)    NOT NULL COMMENT '租户ID',
    object_type    VARCHAR(128)   NOT NULL COMMENT '对象类型',
    object_rid     VARCHAR(256)   NOT NULL COMMENT '对象RID',
    display_name   VARCHAR(512)   NOT NULL COMMENT '显示名称',
    description    TEXT           COMMENT '描述',
    tags           ARRAY<VARCHAR(64)> COMMENT '标签数组',
    properties_json TEXT          COMMENT '属性JSON',
    status         VARCHAR(32)    COMMENT '状态',
    created_at     DATETIME       COMMENT '创建时间',
    updated_at     DATETIME       COMMENT '更新时间',

    -- 倒排索引定义
    INDEX idx_display_name (display_name)
        USING INVERTED
        PROPERTIES("parser" = "unicode", "support_phrase" = "true"),

    INDEX idx_description (description)
        USING INVERTED
        PROPERTIES(
            "parser" = "unicode",
            "support_phrase" = "true",
            "lower_case" = "true"
        ),

    INDEX idx_tags (tags)
        USING INVERTED
        PROPERTIES("parser" = "unicode"),

    INDEX idx_properties (properties_json)
        USING INVERTED
        PROPERTIES("parser" = "unicode", "support_phrase" = "true"),

    INDEX idx_status (status)
        USING INVERTED,

    INDEX idx_object_type (object_type)
        USING INVERTED
)
UNIQUE KEY(tenant_id, object_type, object_rid)
DISTRIBUTED BY HASH(tenant_id) BUCKETS 16
PROPERTIES (
    "replication_num" = "3",
    "enable_unique_key_merge_on_write" = "true",
    "inverted_index_storage_format" = "V2",
    "store_row_column" = "true"
);

#2.3 分词器选择

分词器适用场景配置示例
unicode中英文混合文本"parser" = "unicode""供应链管理" → ["供应", "链", "管理"]
english纯英文文本"parser" = "english""supply chain" → ["supply", "chain"]
chinese纯中文文本"parser" = "chinese"使用结巴分词
无分词精确匹配不指定 parser适用于枚举值

coomia-dip 的分词策略

由于平台支持中英文混合场景,我们统一使用 unicode 分词器。对于需要精确匹配的字段(如 status、object_type),不配置分词器,使用精确索引。

#2.4 全文检索查询语法

SQL
-- 基础全文搜索:MATCH_ANY(OR 语义)
SELECT object_rid, display_name, description
FROM ontology_object_searchable
WHERE tenant_id = 'tenant-001'
  AND display_name MATCH_ANY '供应链 风险'
ORDER BY updated_at DESC
LIMIT 20;

-- 短语搜索:MATCH_PHRASE(要求词序连续)
SELECT object_rid, display_name, description
FROM ontology_object_searchable
WHERE tenant_id = 'tenant-001'
  AND description MATCH_PHRASE '供应链风险评估'
LIMIT 20;

-- 全匹配搜索:MATCH_ALL(AND 语义)
SELECT object_rid, display_name, description
FROM ontology_object_searchable
WHERE tenant_id = 'tenant-001'
  AND description MATCH_ALL '供应链 风险 评估'
LIMIT 20;

-- 前缀搜索:MATCH_PHRASE_PREFIX
SELECT object_rid, display_name
FROM ontology_object_searchable
WHERE tenant_id = 'tenant-001'
  AND display_name MATCH_PHRASE_PREFIX '供应链'
LIMIT 20;

-- 组合条件:全文搜索 + 精确过滤
SELECT object_rid, display_name, description, status, created_at
FROM ontology_object_searchable
WHERE tenant_id = 'tenant-001'
  AND description MATCH_ANY '风险 评估 预警'
  AND status = 'ACTIVE'
  AND created_at >= '2025-01-01'
ORDER BY created_at DESC
LIMIT 50;

#2.5 倒排索引性能基准

在 coomia-dip 测试环境(3 BE 节点,5000 万条记录)上的基准测试:

查询类型无倒排索引有倒排索引加速比
精确匹配 (status)1.2s0.03s40x
关键词搜索 (MATCH_ANY)5.6s0.18s31x
短语搜索 (MATCH_PHRASE)8.3s0.25s33x
模糊搜索 (LIKE '%keyword%')12.1s0.30s40x
组合查询 (全文+过滤+排序)15.2s0.42s36x

#3. 向量索引深度解析

#3.1 向量索引原理:HNSW

Doris 采用 HNSW(Hierarchical Navigable Small World)算法构建向量索引:

Code
Layer 3:  [A] ────────────────── [F]
           |                      |
Layer 2:  [A] ──── [C] ──── [F] ──── [H]
           |        |        |        |
Layer 1:  [A] ─ [B] ─ [C] ─ [D] ─ [F] ─ [G] ─ [H]
           |    |    |    |    |    |    |    |
Layer 0:  [A]-[B]-[C]-[D]-[E]-[F]-[G]-[H]-[I]-[J]

HNSW 关键参数

参数含义推荐值对性能的影响
M每层最大邻居数16-64越大越精确,内存越大
efConstruction构建时搜索宽度200-500越大构建越慢但更精确
efSearch查询时搜索宽度100-300越大查询越慢但更精确

#3.2 创建向量索引表

coomia-dip 中的语义搜索表定义:

SQL
CREATE TABLE ontology_embeddings (
    tenant_id       VARCHAR(64)    NOT NULL COMMENT '租户ID',
    object_type     VARCHAR(128)   NOT NULL COMMENT '对象类型',
    object_rid      VARCHAR(256)   NOT NULL COMMENT '对象RID',
    display_name    VARCHAR(512)   COMMENT '显示名称',
    description     TEXT           COMMENT '描述文本',
    embedding       ARRAY<FLOAT>   NOT NULL COMMENT 'Embedding 向量 (768维)',
    model_version   VARCHAR(32)    COMMENT '模型版本',
    created_at      DATETIME       COMMENT '创建时间',

    -- 向量索引
    INDEX idx_embedding (embedding)
        USING INVERTED
        PROPERTIES(
            "index_type" = "HNSW",
            "metric_type" = "L2",
            "dim" = "768",
            "M" = "32",
            "efConstruction" = "400"
        ),

    -- 文本倒排索引(用于混合查询)
    INDEX idx_display_name (display_name)
        USING INVERTED
        PROPERTIES("parser" = "unicode"),

    INDEX idx_description (description)
        USING INVERTED
        PROPERTIES("parser" = "unicode", "support_phrase" = "true")
)
UNIQUE KEY(tenant_id, object_type, object_rid)
DISTRIBUTED BY HASH(tenant_id) BUCKETS 16
PROPERTIES (
    "replication_num" = "3",
    "enable_unique_key_merge_on_write" = "true"
);

#3.3 向量数据导入

使用 Python SDK 将 Embedding 导入 Doris:

Python
# intelligence-Layer/embedding_indexer.py
import numpy as np
from typing import List, Dict
from sentence_transformers import SentenceTransformer
from doris_stream_loader import DorisStreamLoader

class OntologyEmbeddingIndexer:
    """Ontology 对象 Embedding 索引器"""

    def __init__(
        self,
        model_name: str = "BAAI/bge-base-zh-v1.5",
        doris_host: str = "doris-fe-1",
        doris_port: int = 8030
    ):
        self.model = SentenceTransformer(model_name)
        self.loader = DorisStreamLoader(doris_host, doris_port)
        self.model_version = model_name.split("/")[-1]

    def index_objects(
        self,
        tenant_id: str,
        objects: List[Dict]
    ) -> int:
        """批量索引 Ontology 对象"""
        records = []
        texts = []

        for obj in objects:
            text = f"{obj['display_name']}. {obj.get('description', '')}"
            texts.append(text)

        # 批量编码
        embeddings = self.model.encode(
            texts,
            batch_size=64,
            show_progress_bar=True,
            normalize_embeddings=True
        )

        for obj, embedding in zip(objects, embeddings):
            records.append({
                "tenant_id": tenant_id,
                "object_type": obj["object_type"],
                "object_rid": obj["object_rid"],
                "display_name": obj["display_name"],
                "description": obj.get("description", ""),
                "embedding": embedding.tolist(),
                "model_version": self.model_version,
                "created_at": obj.get("created_at")
            })

        # 批量导入
        result = self.loader.load_json_data(
            database="ontology_db",
            table="ontology_embeddings",
            data=records
        )

        return len(records)

    def search_similar(
        self,
        tenant_id: str,
        query_text: str,
        top_k: int = 10,
        object_type: str = None
    ) -> List[Dict]:
        """语义相似搜索"""
        query_embedding = self.model.encode(
            query_text,
            normalize_embeddings=True
        )

        # 构建 SQL
        embedding_str = ", ".join(str(x) for x in query_embedding.tolist())

        sql = f"""
        SELECT
            object_rid,
            display_name,
            description,
            object_type,
            L2_DISTANCE(embedding, ARRAY[{embedding_str}]) as distance
        FROM ontology_embeddings
        WHERE tenant_id = '{tenant_id}'
        """

        if object_type:
            sql += f" AND object_type = '{object_type}'"

        sql += f"""
        ORDER BY distance ASC
        LIMIT {top_k}
        """

        return self._execute_query(sql)

#3.4 向量检索查询

SQL
-- 基础向量相似度搜索(L2 距离)
SELECT
    object_rid,
    display_name,
    L2_DISTANCE(embedding, ARRAY[0.1, 0.2, ...]) as distance
FROM ontology_embeddings
WHERE tenant_id = 'tenant-001'
ORDER BY distance ASC
LIMIT 10;

-- 余弦相似度搜索
SELECT
    object_rid,
    display_name,
    COSINE_DISTANCE(embedding, ARRAY[0.1, 0.2, ...]) as distance
FROM ontology_embeddings
WHERE tenant_id = 'tenant-001'
ORDER BY distance ASC
LIMIT 10;

-- 内积搜索
SELECT
    object_rid,
    display_name,
    INNER_PRODUCT(embedding, ARRAY[0.1, 0.2, ...]) as score
FROM ontology_embeddings
WHERE tenant_id = 'tenant-001'
ORDER BY score DESC
LIMIT 10;

#3.5 向量索引性能基准

在 coomia-dip 测试环境上的向量检索基准(768 维向量):

数据量暴力搜索HNSW (M=16)HNSW (M=32)HNSW (M=64)
100万850ms12ms8ms6ms
500万4.2s25ms15ms11ms
1000万8.5s45ms28ms18ms
5000万42s120ms75ms52ms

Recall@100 精度

配置efSearch=100efSearch=200efSearch=400
M=1692.3%96.1%98.5%
M=3295.7%98.2%99.3%
M=6497.8%99.1%99.7%

#4. 混合查询:OLAP + 向量 + 全文

#4.1 混合查询架构

coomia-dip 最强大的能力之一是在单个查询中组合 OLAP 分析、向量检索和全文搜索:

Code
用户查询:"找到最近30天创建的、与'供应链风险评估'语义相似的活跃对象"
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   向量检索     全文检索     OLAP过滤
   (语义相似)   (关键词匹配)  (时间+状态)
        │           │           │
        └───────────┼───────────┘
                    ▼
              结果融合排序
              (加权评分)

#4.2 混合查询实现

SQL
-- 混合查询:向量相似度 + 全文匹配 + OLAP 过滤
SELECT
    e.object_rid,
    e.display_name,
    e.description,
    e.object_type,
    L2_DISTANCE(e.embedding, ARRAY[/* query embedding */]) as vector_distance,
    e.created_at
FROM ontology_embeddings e
WHERE e.tenant_id = 'tenant-001'
  -- OLAP 过滤
  AND e.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
  -- 全文检索
  AND e.description MATCH_ANY '供应链 风险'
ORDER BY vector_distance ASC
LIMIT 20;

#4.3 高级混合查询:加权评分

SQL
-- 加权混合评分查询
WITH vector_results AS (
    SELECT
        object_rid,
        display_name,
        description,
        L2_DISTANCE(embedding, ARRAY[/* query embedding */]) as v_dist,
        ROW_NUMBER() OVER (ORDER BY L2_DISTANCE(embedding, ARRAY[/* query embedding */])) as v_rank
    FROM ontology_embeddings
    WHERE tenant_id = 'tenant-001'
    ORDER BY v_dist ASC
    LIMIT 100
),
text_results AS (
    SELECT
        object_rid,
        display_name,
        description,
        ROW_NUMBER() OVER (ORDER BY updated_at DESC) as t_rank
    FROM ontology_object_searchable
    WHERE tenant_id = 'tenant-001'
      AND description MATCH_ANY '供应链 风险 评估'
    LIMIT 100
)
SELECT
    COALESCE(v.object_rid, t.object_rid) as object_rid,
    COALESCE(v.display_name, t.display_name) as display_name,
    -- 加权评分:向量相似度权重 0.6,全文匹配权重 0.4
    (COALESCE(1.0 / v.v_rank, 0) * 0.6 +
     COALESCE(1.0 / t.t_rank, 0) * 0.4) as hybrid_score
FROM vector_results v
FULL OUTER JOIN text_results t ON v.object_rid = t.object_rid
ORDER BY hybrid_score DESC
LIMIT 20;

#4.4 coomia-dip 中的搜索 API 封装

Python
# intelligence-Layer/search/hybrid_search.py
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum

class SearchMode(Enum):
    KEYWORD = "keyword"
    SEMANTIC = "semantic"
    HYBRID = "hybrid"

@dataclass
class SearchRequest:
    tenant_id: str
    query: str
    mode: SearchMode = SearchMode.HYBRID
    object_types: Optional[List[str]] = None
    status_filter: Optional[str] = None
    date_from: Optional[str] = None
    date_to: Optional[str] = None
    top_k: int = 20
    vector_weight: float = 0.6
    text_weight: float = 0.4

@dataclass
class SearchResult:
    object_rid: str
    display_name: str
    description: str
    object_type: str
    score: float
    highlights: List[str] = field(default_factory=list)

class HybridSearchEngine:
    """混合搜索引擎 — 统一向量 + 全文 + OLAP"""

    def __init__(self, doris_connection, embedding_model):
        self.conn = doris_connection
        self.model = embedding_model

    async def search(self, request: SearchRequest) -> List[SearchResult]:
        if request.mode == SearchMode.KEYWORD:
            return await self._keyword_search(request)
        elif request.mode == SearchMode.SEMANTIC:
            return await self._semantic_search(request)
        else:
            return await self._hybrid_search(request)

    async def _hybrid_search(self, request: SearchRequest) -> List[SearchResult]:
        # 1. 生成查询向量
        query_embedding = self.model.encode(
            request.query,
            normalize_embeddings=True
        )

        # 2. 构建混合查询 SQL
        embedding_str = ", ".join(str(x) for x in query_embedding.tolist())

        filters = [f"tenant_id = '{request.tenant_id}'"]
        if request.object_types:
            types_str = ", ".join(f"'{t}'" for t in request.object_types)
            filters.append(f"object_type IN ({types_str})")
        if request.status_filter:
            filters.append(f"status = '{request.status_filter}'")
        if request.date_from:
            filters.append(f"created_at >= '{request.date_from}'")
        if request.date_to:
            filters.append(f"created_at <= '{request.date_to}'")

        where_clause = " AND ".join(filters)

        sql = f"""
        WITH vector_scores AS (
            SELECT object_rid, display_name, description, object_type,
                   1.0 / (1.0 + L2_DISTANCE(embedding, ARRAY[{embedding_str}])) as v_score
            FROM ontology_embeddings
            WHERE {where_clause}
            ORDER BY L2_DISTANCE(embedding, ARRAY[{embedding_str}]) ASC
            LIMIT {request.top_k * 3}
        ),
        text_scores AS (
            SELECT object_rid, display_name, description, object_type,
                   1.0 as t_score
            FROM ontology_object_searchable
            WHERE {where_clause}
              AND (display_name MATCH_ANY '{request.query}'
                   OR description MATCH_ANY '{request.query}')
            LIMIT {request.top_k * 3}
        )
        SELECT
            COALESCE(v.object_rid, t.object_rid) as object_rid,
            COALESCE(v.display_name, t.display_name) as display_name,
            COALESCE(v.description, t.description) as description,
            COALESCE(v.object_type, t.object_type) as object_type,
            (COALESCE(v.v_score, 0) * {request.vector_weight} +
             COALESCE(t.t_score, 0) * {request.text_weight}) as score
        FROM vector_scores v
        FULL OUTER JOIN text_scores t ON v.object_rid = t.object_rid
        ORDER BY score DESC
        LIMIT {request.top_k}
        """

        rows = await self.conn.execute(sql)
        return [
            SearchResult(
                object_rid=row["object_rid"],
                display_name=row["display_name"],
                description=row["description"],
                object_type=row["object_type"],
                score=row["score"]
            )
            for row in rows
        ]

#5. 与独立方案的性能对比

#5.1 全文检索对比:Doris vs Elasticsearch

在 5000 万条 Ontology 对象记录上的对比测试:

指标Doris 倒排索引Elasticsearch 8.x
索引构建时间45 min60 min
存储空间28 GB42 GB
简单关键词搜索 P5035ms25ms
简单关键词搜索 P99180ms120ms
短语搜索 P5065ms45ms
短语搜索 P99280ms200ms
聚合+搜索 P50120ms350ms (需回查)
聚合+搜索 P99450ms1200ms
并发 100 QPS 延迟250ms180ms
数据一致性延迟0 (同源)1-5s (异步同步)

结论:Elasticsearch 在纯搜索场景略有优势,但 Doris 在搜索+分析混合场景中显著领先,且无数据一致性问题。

#5.2 向量检索对比:Doris vs Milvus

在 1000 万条 768 维向量上的对比测试:

指标Doris HNSWMilvus 2.4 (HNSW)
索引构建时间25 min18 min
内存占用12 GB8 GB
Top-10 查询 P5015ms8ms
Top-10 查询 P9945ms25ms
Top-100 查询 P5028ms15ms
Top-100 查询 P9975ms42ms
Recall@10098.2%98.5%
过滤+向量搜索 P5035ms65ms (前/后过滤)
过滤+向量搜索 P9995ms180ms
向量+聚合原生支持不支持

结论:Milvus 在纯向量检索方面更快,但 Doris 在带过滤条件的向量检索和向量+分析混合场景中表现更好。

#6. 索引管理与运维

#6.1 索引构建监控

SQL
-- 查看索引构建进度
SHOW BUILD INDEX FROM ontology_db;

-- 查看表的索引信息
SHOW INDEX FROM ontology_embeddings;

-- 查看 Segment 级别的索引状态
SHOW TABLET FROM ontology_embeddings;

#6.2 索引重建

SQL
-- 删除并重建倒排索引
DROP INDEX idx_description ON ontology_object_searchable;

CREATE INDEX idx_description ON ontology_object_searchable(description)
    USING INVERTED
    PROPERTIES("parser" = "unicode", "support_phrase" = "true");

-- 触发索引重建
BUILD INDEX idx_description ON ontology_object_searchable;

#6.3 存储空间优化

SQL
-- 查看索引存储占用
SHOW DATA FROM ontology_embeddings;

-- 向量索引压缩(PQ 量化)
ALTER TABLE ontology_embeddings
MODIFY INDEX idx_embedding
PROPERTIES("index_type" = "HNSW", "pq_enable" = "true", "pq_subvector_num" = "48");

#7. 生产环境配置清单

#7.1 向量检索优化配置

PROPERTIES
# be.conf — 向量检索相关
enable_vectorized_engine = true
inverted_index_ram_dir_enable = true  # 索引缓存到内存

# 向量索引搜索线程
vector_index_search_thread_pool_size = 16

# HNSW 搜索参数(全局默认)
default_hnsw_ef_search = 200

#7.2 倒排索引优化配置

PROPERTIES
# be.conf — 倒排索引相关
inverted_index_cache_stale_sweep_time_sec = 600
inverted_index_max_buffered_docs = 65536
inverted_index_compaction_enable = true

# 分词器缓存
inverted_index_searcher_cache_limit = 10%

#7.3 内存分配建议

对于同时使用 OLAP 分析、倒排索引和向量索引的 BE 节点:

Code
总内存 64GB 分配方案:
├── 查询执行引擎    30GB (47%)
├── 向量索引缓存    10GB (16%)
├── 倒排索引缓存     8GB (12%)
├── 页缓存           8GB (12%)
├── Compaction        5GB (8%)
└── 系统预留          3GB (5%)

#8. 常见问题与解决方案

#8.1 向量维度不匹配

问题:不同模型生成的 Embedding 维度不同,混用导致查询失败。

解决方案

SQL
-- 使用 model_version 字段隔离不同模型的向量
SELECT * FROM ontology_embeddings
WHERE tenant_id = 'tenant-001'
  AND model_version = 'bge-base-zh-v1.5'
ORDER BY L2_DISTANCE(embedding, ARRAY[...]) ASC
LIMIT 10;

#8.2 倒排索引空间膨胀

问题:TEXT 字段倒排索引占用过多存储。

解决方案

SQL
-- 对长文本字段限制索引 token 数
ALTER TABLE ontology_object_searchable
MODIFY INDEX idx_description
PROPERTIES("max_token_per_doc" = "10000");

#8.3 混合查询性能不稳定

问题:向量检索结果集过大导致后续 OLAP 计算慢。

解决方案

SQL
-- 策略1:先用标量过滤缩小范围,再做向量检索
-- 策略2:使用分区键 + 向量索引,利用分区裁剪
-- 策略3:两阶段查询——先粗筛再精排

#9. 演进路线图

coomia-dip 的 Doris 向量+全文能力演进规划:

阶段时间目标
V1 (当前)2025 Q1基础倒排索引 + HNSW 向量索引
V22025 Q2混合检索评分融合 + BM25 评分
V32025 Q3多模态向量(文本+图像)
V42025 Q4自适应索引选择 + AutoML 评分

#Key Takeaways

  1. 统一引擎是最大价值:Doris 的倒排索引和向量索引使 coomia-dip 避免了维护 Elasticsearch + Milvus 的额外复杂度。虽然单项性能不是最优,但统一引擎带来的数据一致性和运维简化价值远大于性能差距。

  2. 混合查询是核心差异化:在单个 SQL 中组合 OLAP 过滤 + 全文搜索 + 向量检索的能力,是 coomia-dip 相比传统分析平台的核心竞争力。Palantir Foundry 需要多个服务协调才能实现类似功能。

  3. 分词器和索引参数需要按场景精调:unicode 分词器适合中英文混合,HNSW 的 M=32 + efSearch=200 是精度和性能的最佳平衡点。生产环境务必预留足够的内存给索引缓存。

#下一篇预告

S8-03: Apache Nessie 深度解析:Git-like 数据版本控制 — 探讨如何通过 Nessie 实现数据的分支、合并和时间旅行,以及在 coomia-dip 的 Lakehouse 架构中如何管理 Ontology 数据的版本演进。

Tags: #apache-doris #vector-index #inverted-index #hnsw #full-text-search #hybrid-search #coomia-dip #Layer-c