返回博客

存储架构演进:从 9 个组件到 5 个的统一之路

TL;DR

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

存储架构演进:从 9 个组件到 5 个的统一之路

系列:S2 架构全景 · 第 3 篇 | 难度:中级 | 阅读时间:18 分钟

TL;DR

  • 初始设计使用 9 个存储组件(ClickHouse + Qdrant + Elasticsearch + TuGraph + PostgreSQL + Redis + Kafka + MinIO + Nessie/Iceberg),导致 4 倍数据冗余、5 条同步链路、45% 的额外存储成本。
  • Apache Doris 凭借 OLAP 分析 + HNSW 向量索引 + 倒排全文检索的三合一能力,替代了 ClickHouse、Qdrant 和 Elasticsearch,将存储组件从 9 个减少到 6 个(Doris + Nessie/Iceberg + Kafka + Redis + PostgreSQL + MinIO)。
  • 统一的 entity_common / entity_edge / entity_event 三表模型配合 Variant 列类型,实现了本体实例的灵活存储,无需为每种对象类型创建独立表。

#1. 引言:一个"豪华"的存储架构

当我们第一次设计 coomia-dip 的存储层时,犯了一个很多平台项目都会犯的错误:为每种数据访问模式选择了最佳组件

这在理论上无可挑剔——用 ClickHouse 做 OLAP,用 Qdrant 做向量搜索,用 Elasticsearch 做全文检索,用 TuGraph 做图查询。每个组件都是各自领域的佼佼者。

问题在于:当你把它们放在一起时,系统的复杂度不是线性增长,而是指数增长。

#2. V1.0 架构:9 组件的困境

#2.1 原始架构图

Code
+------------------------------------------------------------------+
|                     V1.0 存储架构(9 组件)                         |
+------------------------------------------------------------------+
|                                                                    |
|  +-----------+    +-----------+    +-------------+                |
|  |PostgreSQL |    |   Redis   |    |    Kafka    |                |
|  |(元数据OLTP)|    | (热点缓存)|    |  (事件流)   |                |
|  +-----------+    +-----------+    +-------------+                |
|                                                                    |
|  +-----------+    +-----------+    +-------------+                |
|  |ClickHouse |    |  Qdrant   |    |Elasticsearch|                |
|  |(OLAP分析) |    | (向量搜索) |    | (全文检索)  |                |
|  +-----------+    +-----------+    +-------------+                |
|                                                                    |
|  +-----------+    +-----------+    +-------------+                |
|  |  TuGraph  |    |   MinIO   |    |Nessie+Iceberg|               |
|  | (图数据库) |    | (对象存储) |    | (版本管理)  |                |
|  +-----------+    +-----------+    +-------------+                |
|                                                                    |
+------------------------------------------------------------------+

#2.2 数据同步链路灾难

Code
写入一条 Ontology 实例需要同步到 5 个存储:

Source Data
     |
     v
PostgreSQL (主存储)  ──(1)──> ClickHouse (OLAP 副本)
     |
     ├──(2)──> Qdrant (向量副本)
     |
     ├──(3)──> Elasticsearch (全文索引副本)
     |
     ├──(4)──> TuGraph (图关系副本)
     |
     └──(5)──> Kafka (变更事件)

同步链路: 5 条
数据副本: 5 份 (1 主 + 4 副)
数据冗余: 4x

#2.3 V1.0 的核心问题

问题严重程度具体表现
数据一致性致命5 条同步链路中任何一条延迟或失败,都会导致数据不一致
运维复杂度严重9 个组件的监控、告警、备份、升级
存储成本同一份数据存储 5 次,成本线性增长
开发复杂度每种查询需要对接不同的客户端和查询语言
延迟跨存储查询需要多次网络往返
团队技能团队需要精通 9 种不同的存储技术

#3. 关键发现:Apache Doris 的三合一能力

#3.1 Doris 不只是 OLAP

Apache Doris 在 2.x 版本后实现了三个关键能力的融合:

Code
+------------------------------------------------------------------+
|                    Apache Doris 2.x 能力矩阵                       |
+------------------------------------------------------------------+
|                                                                    |
|  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  |
|  │   OLAP 分析      │  │  HNSW 向量索引   │  │  倒排全文检索    │  |
|  │                  │  │                  │  │                  │  |
|  │ - 列式存储       │  │ - 高维向量       │  │ - 中文分词       │  |
|  │ - 物化视图       │  │ - 近似最近邻     │  │ - BM25 评分     │  |
|  │ - 窗口函数       │  │ - ANN 搜索      │  │ - 短语查询      │  |
|  │ - 聚合查询       │  │ - 支持 768/1536 │  │ - 布隆过滤器    │  |
|  │ - SQL 标准       │  │   维度向量      │  │ - 倒排索引      │  |
|  └─────────────────┘  └─────────────────┘  └─────────────────┘  |
|                                                                    |
|  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  |
|  │ Variant 列类型   │  │  物化视图        │  │  MySQL 协议兼容  │  |
|  │ (半结构化JSON)   │  │  (增量聚合)      │  │  (Sqlg 图查询)   │  |
|  └─────────────────┘  └─────────────────┘  └─────────────────┘  |
|                                                                    |
+------------------------------------------------------------------+

#3.2 替代关系

Code
ClickHouse ──替代──> Doris OLAP 能力
  (列式OLAP)          (同为列式,SQL 兼容更好)

Qdrant ──替代──> Doris HNSW 向量索引
  (专用向量DB)    (2.1+ 版本支持 HNSW 索引)

Elasticsearch ──替代──> Doris 倒排索引
  (全文检索)            (2.0+ 版本支持倒排索引+中文分词)

TuGraph ──替代──> Sqlg (TinkerPop on Doris)
  (图数据库)      (Gremlin -> SQL 编译,走 MySQL 协议)

#4. V3.0 架构:6 个组件的统一

#4.1 新架构图

Code
+------------------------------------------------------------------+
|                V3.0 统一存储架构(6 组件)                           |
+------------------------------------------------------------------+
|                                                                    |
|  外部数据源 -> Kafka -> Flink -> Apache Doris (统一存储+计算)       |
|                                    |                               |
|                    +---------------+---------------+               |
|                    |               |               |               |
|                    v               v               v               |
|              entity_common   entity_edge    entity_event          |
|              (Variant列)     (倒排+Bloom)   (物化视图聚合)         |
|              + HNSW向量索引  + Sqlg图查询                          |
|              + 倒排全文检索                                        |
|                                                                    |
|  +-------------------------------------------------------------+  |
|  |  Nessie + Iceberg (分支 / 时间旅行 / 冷归档)                  |  |
|  +-------------------------------------------------------------+  |
|                                                                    |
|  +----------+  +----------+  +----------+  +----------+          |
|  |  Kafka   |  |  Redis   |  |PostgreSQL|  |  MinIO   |          |
|  | (事件流) |  |(热点缓存)|  |(OLTP元数据)| |(对象存储)|          |
|  +----------+  +----------+  +----------+  +----------+          |
|                                                                    |
|  组件数: 9 -> 6 (Doris + Nessie/Iceberg + Kafka + Redis + PG +   |
|                   MinIO)                                           |
+------------------------------------------------------------------+

#4.2 同步链路简化

Code
V1.0 (5 条同步链路):
  Source -> PG -> ClickHouse
                -> Qdrant
                -> ES
                -> TuGraph
                -> Kafka

V3.0 (1 条同步链路):
  Source -> Kafka -> Flink CDC -> Doris
                                   |
                              (一次写入,多种查询)
                              OLAP / Vector / FullText / Graph

#4.3 成本对比

维度V1.0(9 组件)V3.0(6 组件)改善
存储成本基准 100%55%-45%
数据冗余4x1x-75%
同步链路5 条1 条-80%
运维组件数96-33%
一致性窗口秒级~分钟级毫秒级10-100x
团队技能要求9 种技术4 种核心技术-56%

#5. 三表模型:entity_common / entity_edge / entity_event

#5.1 设计理念

传统做法是为每种 Ontology 对象类型创建独立的数据库表(类似 Palantir 的 Dataset)。这在对象类型频繁变更的场景下会导致频繁的 DDL 操作。

我们采用三表通用模型——所有对象类型共享三张表,通过 Doris 的 Variant 列类型存储灵活的属性数据。

#5.2 entity_common(实体公共表)

SQL
CREATE TABLE entity_common (
    -- 主键
    instance_id       VARCHAR(64)   NOT NULL,
    object_type_id    VARCHAR(64)   NOT NULL,
    world_id          VARCHAR(64)   NOT NULL,

    -- 属性(Variant 列 = 半结构化 JSON,支持列式存储)
    attributes        VARIANT       NOT NULL,

    -- 元数据
    status            VARCHAR(16)   DEFAULT 'ACTIVE',
    created_at        DATETIME      NOT NULL,
    updated_at        DATETIME      NOT NULL,
    created_by        VARCHAR(64),
    version           BIGINT        DEFAULT 1,

    -- 向量(HNSW 索引)
    embedding_768     ARRAY<FLOAT>  NULL,  -- 小模型向量
    embedding_1536    ARRAY<FLOAT>  NULL,  -- 大模型向量

    -- 全文检索(倒排索引)
    search_text       TEXT          NULL,

    -- 索引
    INDEX idx_type     (object_type_id) USING INVERTED,
    INDEX idx_world    (world_id) USING INVERTED,
    INDEX idx_status   (status) USING INVERTED,
    INDEX idx_search   (search_text) USING INVERTED
        PROPERTIES("parser" = "chinese"),
    INDEX idx_vec_768  (embedding_768) USING INVERTED
        PROPERTIES("index_type" = "HNSW"),
    INDEX idx_vec_1536 (embedding_1536) USING INVERTED
        PROPERTIES("index_type" = "HNSW")
)
DISTRIBUTED BY HASH(instance_id) BUCKETS 32
PROPERTIES (
    "replication_num" = "3",
    "enable_unique_key_merge_on_write" = "true"
);

#5.3 Variant 列的魔力

SQL
-- 写入不同类型的实体,无需 DDL 变更

-- 写入 Employee 类型
INSERT INTO entity_common (instance_id, object_type_id,
    world_id, attributes) VALUES (
    'emp_001', 'Employee', 'world_prod',
    '{"name": "Zhang Wei",
      "department": "Engineering",
      "salary": 85000,
      "skills": ["Java", "Python", "gRPC"],
      "hire_date": "2024-03-15"}'
);

-- 写入 Equipment 类型(完全不同的属性结构)
INSERT INTO entity_common (instance_id, object_type_id,
    world_id, attributes) VALUES (
    'equ_001', 'Equipment', 'world_prod',
    '{"model": "Excavator CAT 320",
      "serial_number": "CAT-2024-X320-001",
      "location": {"lat": 31.23, "lng": 121.47},
      "maintenance_due": "2024-12-01",
      "operating_hours": 2450}'
);

-- 查询时直接访问 Variant 列的子字段
SELECT
    instance_id,
    attributes['name'] AS name,
    attributes['salary'] AS salary
FROM entity_common
WHERE object_type_id = 'Employee'
  AND world_id = 'world_prod'
  AND CAST(attributes['salary'] AS INT) > 80000;

#5.4 entity_edge(关系边表)

SQL
CREATE TABLE entity_edge (
    -- 边的唯一标识
    edge_id           VARCHAR(64)   NOT NULL,

    -- 源和目标
    source_id         VARCHAR(64)   NOT NULL,
    source_type       VARCHAR(64)   NOT NULL,
    target_id         VARCHAR(64)   NOT NULL,
    target_type       VARCHAR(64)   NOT NULL,

    -- 关系类型
    relation_type     VARCHAR(64)   NOT NULL,
    world_id          VARCHAR(64)   NOT NULL,

    -- 关系属性(可选)
    properties        VARIANT       NULL,

    -- 元数据
    created_at        DATETIME      NOT NULL,
    created_by        VARCHAR(64),

    -- 索引
    INDEX idx_source   (source_id) USING INVERTED,
    INDEX idx_target   (target_id) USING INVERTED,
    INDEX idx_rel_type (relation_type) USING INVERTED,
    INDEX idx_world    (world_id) USING INVERTED,
    INDEX idx_bloom_src (source_id) USING BLOOM_FILTER,
    INDEX idx_bloom_tgt (target_id) USING BLOOM_FILTER
)
DISTRIBUTED BY HASH(edge_id) BUCKETS 16;

#5.5 entity_event(事件表)

SQL
CREATE TABLE entity_event (
    -- 事件标识
    event_id          VARCHAR(64)   NOT NULL,
    instance_id       VARCHAR(64)   NOT NULL,
    object_type_id    VARCHAR(64)   NOT NULL,
    world_id          VARCHAR(64)   NOT NULL,

    -- 事件类型
    event_type        VARCHAR(32)   NOT NULL,
    -- CREATE | UPDATE | DELETE | RELATION_ADD |
    -- RELATION_REMOVE | DERIVED_RECOMPUTE

    -- 事件内容
    before_state      VARIANT       NULL,   -- 变更前
    after_state       VARIANT       NULL,   -- 变更后
    changed_fields    ARRAY<VARCHAR(64)> NULL,

    -- 元数据
    event_time        DATETIME      NOT NULL,
    user_id           VARCHAR(64),
    trace_id          VARCHAR(64),

    -- 索引
    INDEX idx_instance (instance_id) USING INVERTED,
    INDEX idx_type     (event_type) USING INVERTED,
    INDEX idx_world    (world_id) USING INVERTED
)
DISTRIBUTED BY HASH(instance_id) BUCKETS 16
PARTITION BY RANGE(event_time) (
    PARTITION p202401 VALUES LESS THAN ('2024-02-01'),
    PARTITION p202402 VALUES LESS THAN ('2024-03-01'),
    -- 按月分区,支持高效时间范围查询和冷数据清理
)
PROPERTIES (
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "MONTH",
    "dynamic_partition.end" = "3",
    "dynamic_partition.prefix" = "p"
);

#5.6 三表协作的查询示例

SQL
-- 查询: 获取 Employee 及其关联的 Equipment,包含变更历史

-- 1. 获取 Employee 实例
SELECT ec.instance_id, ec.attributes
FROM entity_common ec
WHERE ec.object_type_id = 'Employee'
  AND ec.world_id = 'world_prod'
  AND ec.instance_id = 'emp_001';

-- 2. 获取关联的 Equipment
SELECT
    ec.instance_id,
    ec.attributes['model'] AS model,
    ee.relation_type
FROM entity_edge ee
JOIN entity_common ec
    ON ee.target_id = ec.instance_id
WHERE ee.source_id = 'emp_001'
  AND ee.relation_type = 'operates'
  AND ee.world_id = 'world_prod';

-- 3. 获取变更历史
SELECT event_type, changed_fields,
       before_state, after_state, event_time
FROM entity_event
WHERE instance_id = 'emp_001'
  AND world_id = 'world_prod'
ORDER BY event_time DESC
LIMIT 20;

#6. Nessie + Iceberg:版本化湖仓

#6.1 为什么需要版本化?

coomia-dip 的核心概念之一是 World(世界分支)。每个 World 是一个完整的数据快照分支,支持:

  • 场景推演:创建 World 分支,修改数据,观察影响,而不影响生产
  • 时间旅行:查询任意历史时刻的数据状态
  • 分支合并:将推演结果合并回生产

#6.2 Nessie 分支管理

Code
main (Production World)
  |
  +--- dev/feature-001 (Development Branch)
  |     |
  |     +--- commit_a1b2c3 (Schema change)
  |     +--- commit_d4e5f6 (Data migration)
  |
  +--- scenario/supply-chain-disruption
  |     |
  |     +--- commit_g7h8i9 (What-if: supplier fails)
  |     +--- commit_j0k1l2 (Impact assessment)
  |
  +--- scenario/price-optimization
        |
        +--- commit_m3n4o5 (Price adjustment)

#6.3 Iceberg 表格式

Code
Nessie (Branch/Tag Manager)
     |
     v
Iceberg (Table Format)
     |
     +--- Metadata Layer
     |    ├── snapshot_001.json
     |    ├── snapshot_002.json
     |    └── manifest_list.avro
     |
     +--- Data Layer (Parquet Files on MinIO)
          ├── data_001.parquet
          ├── data_002.parquet
          └── data_003.parquet

#6.4 热/温/冷数据分层

Code
+---------+  实时写入  +---------+  定期归档  +---------+
|  Doris  | ---------> | Iceberg | ---------> |  MinIO  |
|  (热)   |            |  (温)   |            |  (冷)   |
+---------+            +---------+            +---------+
 < 7 天                 7-90 天                > 90 天
 毫秒级查询             秒级查询               分钟级查询
 SSD 存储               SSD/HDD               HDD/S3

#7. Kafka 在存储架构中的角色

Code
+------------------------------------------------------------------+
|                    Kafka 在存储架构中的位置                         |
+------------------------------------------------------------------+
|                                                                    |
|  [外部数据源]                                                      |
|       |                                                            |
|       v                                                            |
|  +----------+                                                      |
|  |  Kafka   |  <-- 数据接入的统一入口                               |
|  |          |                                                      |
|  | Topics:  |                                                      |
|  | - ontology.ingestion.{type}    数据接入                          |
|  | - ontology.change.{type}       变更事件                          |
|  | - audit.events                 审计事件                          |
|  | - pipeline.triggers            管道触发                          |
|  | - derived.invalidation         派生属性失效                      |
|  | - reasoning.triggers           推理触发                          |
|  | - cross-Layer.coordination     跨Plane协调                      |
|  +----------+                                                      |
|       |                                                            |
|       v                                                            |
|  +-----------+        +-----------+                                |
|  | Flink CDC | -----> |   Doris   |                                |
|  +-----------+        +-----------+                                |
|                                                                    |
+------------------------------------------------------------------+

#8. Redis 缓存策略

#8.1 缓存层级

Code
请求到达
     |
     v
+------------------+
| L1: 进程内缓存    |  Caffeine (Java) / lru_cache (Python)
| TTL: 10s         |  容量: 10K entries per process
+------------------+
     | miss
     v
+------------------+
| L2: Redis 缓存    |  Redis Cluster
| TTL: 5min        |  容量: 按需扩展
+------------------+
     | miss
     v
+------------------+
| L3: Doris 查询    |  entity_common / entity_edge
| 无 TTL           |  权威数据源
+------------------+

#8.2 缓存键设计

Code
Schema 缓存:
  schema:{world_id}:{object_type_id}:{version}
  TTL: 30min (Schema 变更触发主动失效)

实例缓存:
  instance:{world_id}:{instance_id}:{version}
  TTL: 5min

派生属性缓存:
  derived:{world_id}:{instance_id}:{property_id}:{version}
  TTL: 按计算策略决定 (CACHED 策略 = 10min)

查询结果缓存:
  query:{world_id}:{query_hash}
  TTL: 1min (查询结果变化频繁)

#9. PostgreSQL:OLTP 元数据

#9.1 为什么不用 Doris 存元数据?

Doris 是 OLAP 引擎,擅长大批量扫描分析,但在以下场景中 PostgreSQL 更适合:

场景DorisPostgreSQL
单行精确查询慢(列式扫描开销)快(B-Tree 索引)
高频写入小事务不适合(批量写入优化)适合(WAL + MVCC)
ACID 事务有限支持完整支持
外键约束不支持支持

#9.2 PostgreSQL 中存储的数据

Code
PostgreSQL 元数据库:
├── tenant_config      (租户配置)
├── organization       (组织结构)
├── user_account       (用户账户)
├── role_assignment    (角色分配)
├── policy_definition  (策略定义)
├── world_metadata     (World 元数据)
├── pipeline_definition (Pipeline 定义)
├── scheduler_config   (调度配置)
├── connection_config  (连接配置)
└── system_config      (系统配置)

#10. 存储适配器 SPI 设计

#10.1 为什么需要 SPI?

虽然 Doris 是当前的选择,但我们通过 SPI(Service Provider Interface)保留了替换存储引擎的能力:

Java
// 存储适配器接口
public interface OntologyStorageAdapter {

    // 实例 CRUD
    OntologyInstance create(
        WorldContext world,
        String objectTypeId,
        Map<String, Object> attributes);

    Optional<OntologyInstance> findById(
        WorldContext world,
        String instanceId);

    List<OntologyInstance> query(
        WorldContext world,
        OntologyQuery query);

    void update(
        WorldContext world,
        String instanceId,
        Map<String, Object> changes);

    void delete(
        WorldContext world,
        String instanceId);

    // 关系管理
    void createRelation(
        WorldContext world,
        String sourceId,
        String targetId,
        String relationType);

    List<Relation> getRelations(
        WorldContext world,
        String instanceId,
        RelationDirection direction);

    // 向量搜索
    List<VectorSearchResult> vectorSearch(
        WorldContext world,
        float[] queryVector,
        int topK,
        VectorSearchOptions options);

    // 全文检索
    List<FullTextSearchResult> fullTextSearch(
        WorldContext world,
        String query,
        FullTextSearchOptions options);
}

// Doris 实现
@Service
@Primary
public class DorisOntologyStorageAdapter
    implements OntologyStorageAdapter {
    // Doris SQL 实现...
}

// 未来可能的替代实现
// public class StarRocksOntologyStorageAdapter
//     implements OntologyStorageAdapter { ... }

#10.2 SPI 注册机制

Java
// application.yml
onto:
  storage:
    adapter: doris  # 或 starrocksm 或 custom
    doris:
      fe-host: doris-fe
      fe-port: 9030
      database-prefix: onto_

#11. 数据库级别的 World 隔离

#11.1 Project → Doris Database 映射

Code
Tenant: acme_corp
  └── Organization: engineering
       └── Space: product_dev
            └── Project: supply_chain
                 |
                 +---> Doris DB: onto_acme_engineering_supply_chain
                 |     ├── entity_common
                 |     ├── entity_edge
                 |     └── entity_event
                 |
                 +---> MinIO Bucket: onto-acme-engineering-supply-chain
                 |
                 +---> Nessie Namespace: acme.engineering.supply_chain
                       ├── Branch: main (Production World)
                       ├── Branch: dev/feature-001
                       └── Branch: scenario/disruption-sim

#11.2 World 隔离通过 WHERE 条件实现

SQL
-- 所有查询都自动附加 world_id 过滤
-- OntologyRuntimeService 在查询构建时注入

SELECT * FROM entity_common
WHERE world_id = 'world_prod'      -- 强制 World 隔离
  AND object_type_id = 'Employee'
  AND CAST(attributes['department'] AS VARCHAR)
      = 'Engineering';

-- 如果遗漏 world_id 过滤,OntologyRuntimeService
-- 会抛出 WORLD_CONTEXT_REQUIRED 异常
-- 这是架构级别的强制,不可绕过

#12. 与其他存储方案的对比

方案OLAP向量全文版本化复杂度
9 组件(V1.0)ClickHouseQdrantESTuGraphNessie极高
Doris 统一(V3.0)DorisDoris HNSWDoris 倒排Sqlg on DorisNessie
Snowflake+PineconeSnowflakePineconeSnowflakeTime Travel
Databricks+ChromaSpark SQLChromaDelta LakeDelta Lake
PostgreSQL+pgvectorPG(慢)pgvectorPG FTSApache AGE

#12.1 Doris 方案的局限性

Code
已知局限:
1. 向量搜索精度: Doris HNSW 精度略低于专用向量数据库
   影响: 对于 RAG 等对召回率要求极高的场景,可能需要额外优化
   缓解: 可以通过调整 HNSW 参数(ef_construction, M)提高精度

2. 全文检索功能: 不如 Elasticsearch 丰富
   影响: 复杂的 NLP 查询(同义词、模糊匹配、拼音搜索)
   缓解: 80% 的查询场景 Doris 倒排索引足够

3. 图查询性能: Sqlg 编译 Gremlin->SQL 有额外开销
   影响: 深度遍历(> 3 跳)性能下降
   缓解: 物化视图预计算常用图查询路径

4. 写入延迟: Doris 批量写入优化,单条写入延迟较高
   影响: 实时单条更新场景
   缓解: 通过 Kafka 缓冲 + 微批量写入

#13. 迁移路径:从 V1.0 到 V3.0

Code
Phase 1: 部署 Doris,双写
  - 保持原有 ClickHouse/Qdrant/ES 运行
  - 新数据同时写入 Doris
  - 验证 Doris 查询结果与原有系统一致

Phase 2: 读取切换
  - OLAP 查询从 ClickHouse 切换到 Doris
  - 向量搜索从 Qdrant 切换到 Doris HNSW
  - 全文检索从 ES 切换到 Doris 倒排索引

Phase 3: 清理旧组件
  - 停止向 ClickHouse/Qdrant/ES 写入
  - 下线旧组件
  - 回收资源

历时: 约 6 周(含充分测试)
风险: 低(双写阶段可随时回滚)

#Key Takeaways

  1. "为每种查询选最好的组件"是一个陷阱。 当你有 5 种不同的存储引擎时,数据同步的复杂度会吞噬所有性能优势。Apache Doris 的三合一能力(OLAP + 向量 + 全文)让我们用 1 个组件替代了 4 个,消除了 4 倍数据冗余和 5 条同步链路。

  2. Variant 列类型是灵活本体存储的关键。 entity_common 表的 attributes VARIANT 列让任何类型的本体实例都能存入同一张表,避免了为每种对象类型做 DDL 变更。这与 Doris 的列式存储完美契合——Variant 内部仍然是列式存储,查询性能不打折。

  3. 版本化不是可选功能,是核心架构。 Nessie + Iceberg 的分支管理让 World 概念从"想法"变成了"实现"。每个 Nessie 分支就是一个 World,分支创建是 O(1) 操作(只创建元数据指针),数据共享底层 Iceberg 文件,直到发生修改才 Copy-on-Write。

下一篇预告: [S2-04] 我们的 Ontology 内核:一切操作必须经过本体层——深入理解为什么直接访问数据库是技术红线,以及 OntologyRuntimeService 如何成为平台的"数据宪法"。

Tags: #storage #doris #iceberg #nessie #vector-search #fulltext #olap #data-architecture #coomia-dip #智策平台