这几年实时分析圈子的变化,真的比我预想中要快。Apache Doris 是开源实时数据仓库里绕不开的项目,我和团队在好几套系统里用过它;SelectDB 则是 Doris 背后的商业化云服务,把同一套引擎搬到云上做成托管产品。真正让我意识到需要重新梳理思路的,是 AI 大规模落地之后:业务方来找我,问的最多的不再是“报表多久能刷新”,而是“能不能让 AI 直接用库里的最新数据,回答客户、跑分析、辅助决策”。这个转变看起来只是问题形式变了,实际上背后是实时分析的整体范式在变。
参考我和几个数据团队这两年的实践,我把 AI 时代的实时分析拆成三个可以独立落地、又可以叠加使用的范式,后面会逐个展开讲原理、给示例、晒实操心得。这篇文章适合谁看?一是正在做数据架构选型、被“实时 + AI”两个词夹击的工程师;二是已经在用 Doris 但只拿它跑报表、想往 AI 场景延伸的团队;三是想评估 SelectDB 这类云服务值不值的负责人。先把整个体系在脑子里立起来,后面落地时就不容易走偏。
1. 为什么 AI 时代要重新定义实时分析
1.1 分析需求正在从“给人看”转向“给模型用”
过去做实时分析,目标用户是人。运营看大屏、管理层看 BI、研发看监控,查询结果最终都要落到人的理解和决策上。人的响应速度慢,对延迟的容忍度高,报表一分钟刷新一次已经很不错。但 AI 时代不一样,数据消费方变成了大模型、推荐系统、风控引擎和各类 Agent。模型不会像人一样等报表,它需要的是在几百毫秒内拿到最新、最准确的数据作为上下文。典型场景就有几个:推荐系统要根据秒级行为更新用户特征;客服机器人要用最新订单和工单信息生成回答;风控模型要实时校验每一笔交易。这些场景里,数据晚一分钟,结论就可能完全跑偏。这也是我判断“实时分析三大范式”需要被重新定义的根本原因——消费对象变了,技术架构必须跟着变。
1.2 旧架构断裂在哪里
很多团队现在的实时链路还是老一套:业务库发消息到 Kafka,Flink 做流式计算,结果写入 Redis、MySQL 或者 Elasticsearch,再叠加一个 ClickHouse 或者 Doris 给分析师用。链路一旦拉长,问题就跟着来。第一是延迟叠加,每多一跳就多几十甚至几百毫秒;第二是数据一致性,同一份数据在 Redis、MySQL、OLAP 里各存一份,经常出现对不上的情况;第三是运维成本,消息、流、库、缓存全要照顾,任何一个环节抖动都会影响整体。我见过不少团队最后绕不开一个结论:与其维护一条长链路,不如把“导入、存储、查询、服务”四件事放在一个引擎里做。这正是 Doris 早期定位里“One Data Architecture”思路的落地。它的列式存储、向量化执行、多表模型,让它在做复杂分析的同时也能承接高并发服务化查询,天然适合当作 AI 时代的数据底座。
1.3 三大范式到底是什么
先把后面要展开的框架交代清楚,后面好对号入座。
我把 AI 时代的实时分析拆成三个相互独立又能叠加的范式。第一个是“实时数仓范式”,核心是把数仓从报表工具变成数据服务,用高并发点查能力支撑前台业务系统,解决从“查得动”到“查得快”的问题。第二个是“湖仓一体范式”,核心是把企业数据湖里的海量原始数据,通过统一元数据的方式接入实时数仓,让同一份数据既能喂给模型做训练,又能支撑分析查询,解决“数据孤岛”和“一份数据多处存储”的问题。第三个是“AI 融合范式”,核心是让数仓直接给大模型和 Agent 提供工具能力,包括向量检索、知识库召回和 Text-to-SQL,解决“模型拿不到实时数据”的问题。
这三者不是互相替代的关系,而是层层递进。底下必须先有实时数仓把数据管好,再用湖仓一体把数据范围扩大,最后 AI 才能在完整、新鲜的数据上做理解与决策。后面每个范式我都会拆解原理、给出实操建表和示例,尽量让你看完能直接抄作业。
2. 范式一:实时数仓——把数据仓库做成数据服务
2.1 从“跑批出报表”到“高并发 API 化”
第一范式解决的是最朴素的需求:数据要快。这里的快不是指一个复杂分析从十秒变一秒,而是指业务系统直接调用数仓,在毫秒级返回结果。最典型的场景是用户画像。以前做画像,常见做法是凌晨批算,生成一张大宽表,白天用 Redis 缓存提供查询。但一到数据量上来,宽表动辄几亿行,字段几十上百个,Redis 内存根本装不下;想支持多维组合过滤,比如“最近 30 天活跃且消费金额大于一千元且偏好某类商品”这种条件,Redis 的简单的 key-value 结构也很吃力。
Apache Doris 在高频服务化查询上有一套比较成熟的打法。核心是 Unique Key 表模型配合前缀索引与分桶裁剪。导入时按主键去重并标记最新版本,查询时通过前缀索引快速定位到少量分桶,再在桶内做稠密索引扫描。这套机制让 Doris 可以在单集群承担每秒上千甚至更高 QPS 的点查,且延迟 p99 稳定在百毫秒以内。相比 Redis,它赢在三点:一是 SQL 表达能力完整,各种条件组合、聚合、Join 都能写;二是数据天然有历史版本,不会因为覆盖而丢失;三是容量大得多,几十 TB 都在设计范围之内。这也是我认为“实时数仓范式”的第一层,把数据仓库从给人看的系统变成给系统用的服务。
2.2 关键机制拆解:为什么它能点得快
理解 Doris 的高并发能力,得先看三层设计。
第一层是存储模型。Doris 的三种表模型里,Duplicate Key 适合明细追加,Aggregate Key 适合预聚合,Unique Key 适合主键更新。高并发点查场景几乎全部落在 Unique Key 上。早期 Unique Key 走 Merge-On-Read,查询时要合并多个版本的数据,导致点查和聚合都会有额外开销。从 2.0 版本开始,官方主推 Merge-On-Write,数据导入时就把同主键的历史行标记删除、新行直接可见。这样查询时不再需要临时合并,代价是写路径稍重,但对查询性能的提升非常明显。现在的新版本里,MOW 已经是高并发场景的默认选择。
第二层是索引结构。Doris 每一列都有对应的索引信息,表构建时会在前缀列上建立稀疏排序索引,再配合分桶的哈希裁剪,让查询在绝大多数情况下只需要扫描非常小的数据范围。前缀索引最长 36 字节,所以在设计上有讲究:前缀列尽量选区分度高的整数或短字符串,像是 user_id、order_id 这种,而不是长字符串,否则截断会导致索引价值打折。
第三层是 Compaction 机制。实时导入会产生很多小版本,如果不处理,查询会退化。Doris 的 Compaction 在后台持续把小版本合并成大版本,有 Base Compaction 和 Cumulative Compaction 两层配合。它的设计目标是让 Compaction 对查询造成的影响尽量小,这一点我在实际压测中确认过:持续导入的场景下,点查延迟依然能保持稳定。
2.3 实操:一张能支撑高并发服务的画像表怎么建
直接给一份能抄的例子。假设我们要建用户画像宽表,主键是 user_id,指标字段若干,需要按最近消费时间和城市过滤,同时高频读取用户状态。
CREATE TABLE user_profile ( user_id BIGINT NOT NULL, city_id INT NOT NULL, last_pay_time DATETIME NOT NULL, gender TINYINT, level INT, total_amount DECIMAL(12,2), recent_views INT, attribute MAP<STRING, STRING> ) UNIQUE KEY(user_id) DISTRIBUTED BY HASH(user_id) BUCKETS 64 PROPERTIES ( "unique_key_merge_on_write" = "true", "replication_num" = "1" );几个实操要点。第一,分桶数不要拍脑袋。官方建议单个分桶的数据量控制在 10GB 到 20GB 之间,可以先预估数据总量,再倒推桶数。比如总数据量约 50GB,分 4 到 8 个桶比较合适,桶太多会放大元数据和 Compaction 压力。第二,查询条件要尽量落到前缀列上。表的主键是 user_id,点查指定 user_id 可以精确裁剪;如果业务经常按 city_id 过滤,就把 city_id 放到前缀列组合里,但注意前缀列顺序不能随意调。第三,不要在每列上都加索引,尤其不要对低区分度的列强行建索引。Doris 的倒排索引(INVERTED)适合全文和子字符串匹配,但用多了会增加写放大。
另外一个建议是给高频过滤条件建物化视图。比如系统经常按 city_id 聚合统计,可以针对这个维度单独建一个 Rollup 或异步物化视图,查询走视图而不是全表扫描。物化视图的选择要克制,只覆盖真正高频的查询模式,否则会变成存储和写入的负担。
最后说下实测体感。我们之前压测过一个 10 亿行量级的 Unique 表,纯点查 QPS 到 1500 左右,p99 延迟在 80ms 上下;加上 city_id 等 2 到 3 个过滤条件的复合查询,QPS 会掉到 600 到 800,但延迟依然在百毫秒量级。这个表现足够支撑绝大多数业务 API 的实时数据需求。
提示:如果并发请求来自同一个应用,建议走 JDBC 或 HTTP 的预编译语句(PreparedStatement),并且开启 Doris 的 Query Cache,能显著降低重复 SQL 的解析开销。踩过坑的都知道,瓶颈常常不在数据扫描,而在重复解析和网络往返。
3. 范式二:湖仓一体——用 Doris 统一湖与仓
3.1 湖与仓的矛盾,最后都变成“数据要两份”
数据湖这几年在企业里铺得很开,典型组合是 Hive 存原始明细,Iceberg/Hudi/Paimon 做增删改和快照。好处是开放、便宜、能存大规模原始数据。坏处也明显,尤其是查询性能:Iceberg 和 Hudi 的查询通常要起 Spark 或 Flink 作业,一次全量扫描经常等几分钟,这对分析师和 AI 应用来说都不友好。数据仓库这边相反,列存、索引、向量化执行都在为性能服务,但想吃到湖里的数据就得先做一轮又一轮的同步,把数据从湖搬到仓。
结果就是同一份数据在湖里一份、在仓里一份,两份还要定版本对齐。我们团队早期也是这么干的,半夜用调度任务导数据,第二天白天才发现数据口径对不上,非常痛苦。后来切到 Doris 的 Multi-Catalog 方式,把 Hive/Iceberg/Hudi/Paimon 都注册成外部 Catalog,Doris 作为一个入口统一查询湖内数据,需要加速的表再用物化视图落到内部存储。这样既不用把数据全搬走,又能让高频查询获得数仓级性能。
3.2 用 Multi-Catalog 查湖,用物化视图加速
建外部 Catalog 很简单,以 Hive 为例:
CREATE CATALOG hive_catalog PROPERTIES ( "type" = "hms", "hive.metastore.uris" = "thrift://172.16.10.10:9083" );注册之后就能直接查表了。Doris 和外部表的交互不是简单的“拷一份”,而是尽量做下推:分区裁剪会传到 MetaStore,谓词条件会传到文件扫描层,列裁剪只读需要的列。这些看起来是基本能力,但在实际性能上差别很大,尤其是查 Iceberg 时,能否把 condition 下推到 manifest 层直接决定了扫描量。我用同一张 Iceberg 表做过对比,支持下推的查询扫描行数能少一个数量级。
对于频繁访问的湖表,我建议在 Doris 里建异步物化视图,而不是长期依赖外表直查:
CREATE MATERIALIZED VIEW mv_user_daily BUILD DEFERRED REFRESH AUTO ON MANUAL AS SELECT user_id, city_id, DATE(last_pay_time) AS pay_date, SUM(total_amount) AS amount, COUNT(*) AS cnt FROM hive_catalog.db1.orders GROUP BY user_id, city_id, DATE(last_pay_time);物化视图相当于把外部查询结果固化到 Doris 的内部列存里,后续的报表、特征查询直接走内表,性能和稳定性都有保证。刷新策略我一般用 ON MANUAL + 定时任务,凌晨刷新昨天分区,白天只查不改。自动 refresh 在数据湖场景里容易触发大量扫描,需要谨慎配置。
这里有一个常见争论:湖里的数据要不要回填到仓里。我的观点是不必全量回填,只物化真正被高频查询的核心表。原始明细、日志、未清洗的数据继续留在湖里,由统一元数据管理。Doris 的 Catalog 能让你在同一个会话里 Join 内表和外表,这在免去数据搬迁的同时保持分析能力,这是湖仓一体比传统“搬迁式”架构舒服很多的地方。
3.3 AI 训练与特征场景怎么用上湖仓一体
湖仓一体对齐到 AI 场景,核心价值是解决训练数据和特征数据的分裂问题。AI 团队通常需要三类数据:全量原始数据做训练、实时特征做推理、标签数据做评估。如果没有统一入口,这三类数据会散落在数据湖、Redis、OLAP 三处,AI 工程师光是找数据就要花很多时间。
用 Doris + 湖仓一体,可以这样组织:原始数据继续存在湖上 Iceberg/Hudi 表;Doris 通过 Catalog 读取湖表,用 SQL 算出特征宽表;特征宽表物化成内部表供在线推理读取;同时训练集可以直接从 Doris 导出到对象存储,交给训练平台。批与流在这里可以共用一套数据模型,Spark 或 Flink 写湖,Routine Load 或 Flink CDC 写仓,两边用同一个 Catalog 元数据管理,最后 AI 团队只用面对 Doris 一个查询入口。
我自己的经验是,这个范式落地时要特别注意两点。第一,Catalog 命名规范要提前定好,比如生产环境只允许通过统一 Catalog 访问,禁止直接连底层文件,否则权限会失控。第二,千万要对齐时间语义。湖上的 Iceberg 表快照和 Doris 内表的实时数据天然有时间差,做训练集时要在 SQL 里显式指定时间范围,否则容易把“不同时间口径”的数据混在一起,模型效果很难复现。这个问题我们吃过亏,后来所有训练集导出任务都固定使用统一的时间分区条件。
4. 范式三:AI 融合——实时分析变成 AI 的工具箱
4.1 为什么实时数仓要做向量检索
AI 应用落地最常见的需求是语义检索和知识召回。比如客服机器人要先从知识库或历史工单里找出“语义相似”的记录,再结合这些记录生成回答。传统实现是单独部署一个向量数据库,把文档或记录切块、生成 embedding、写入向量库。而业务数据仍在 Doris 里,变成两套系统,数据双写、口径分裂、联合过滤困难。
Doris 从 2.1 开始内置了向量能力,把向量字段和普通字段放在同一张表里。你可以建一个带 Array 类型的表,对向量列建 HNSW 或 IVF 索引,然后在一条 SQL 里同时完成业务条件过滤和向量相似度排序。这是向量数据库很难做到的事情:向量库通常很难高效表达“状态为已支付、城市在北京、时间在最近 7 天”这种结构化条件,Doris 可以把结构化条件压到最小范围再去做向量计算。
举例:
SELECT id, title, content, distance(embedding, [0.12, 0.34, ...]) AS score FROM doc_knowledge WHERE status = 'active' ORDER BY score ASC LIMIT 5;这里distance()函数按向量距离返回最相似的记录。从工程角度看,Doris 的向量索引演进还在持续优化中,单就大规模向量 + 高并发检索这个组合来说,它比“向量库 + 关系库”拼装出来的方案简单不少。选型时要留意向量维度、距离函数是否和索引类型匹配,后面我会在问题章节单独讲。
4.2 RAG 与知识库:从文档知识到实时数据
传统 RAG 的流程是文档切块 → 向量化 → 存向量库 → 检索 → 喂给大模型。这套流程在数据不常变化的场景下没问题,但企业内部的客服、销售、运维场景,数据是实时变化的。文档库里不会更新“这个客户今天刚下单”“这台设备刚刚报警”,而这些问题恰恰需要实时数据回答。
更务实的做法是把 RAG 的“知识库”升级为“数据库”,让大模型需要时直接从 Doris 拿实时明细。比如智能客服场景,用户的提问先做 embedding,然后在 Doris 一张工单表里做向量召回,同时用 user_id、时间、订单状态等结构化条件做过滤。回答的上下文就不再是几段过期的静态文档,而是“该用户最近遇到的实际问题”。这种做法把 RAG 的元数据过滤从“简单标签”升级成了完整的 SQL 能力,可控性和准确率都会好很多。
实操上我会这么做:在 Doris 建一张知识混合表,既存文本切片和 embedding,也存业务标签列;建一个 API 服务封装检索逻辑,应用侧只传参数。这样模型不会直接看到数据库结构,权限和 SQL 风险都被挡在 API 层。另外建议每次批量刷新 embedding 时记录版本号,检索时按版本过滤,避免新旧向量混用导致的召回混乱。
4.3 Text-to-SQL 与 Agent:让模型学会调用实时数据
接下来是 AI 融合范式里最热的方向:让大模型直接通过自然语言查数。很多团队光速把大模型接上数据库连接串,很快发现三个问题:模型生成的 SQL 经常有语法或语义错误;没有权限控制,模型可能查任意表任意字段;一次生成的全表扫描直接拖垮线上集群。
我建议的正确姿势是两层隔离。外层是 Agent,负责理解自然语言、规划动作、调用工具;内层是受控的数据服务,只暴露预先定义好的 SQL 模板或受限查询接口,Agent 在模板里填参数。Doris 扮演的就是内层数据服务的角色,通过 HTTP API 或精心封装的 JDBC 接口接收参数化查询。如果需要让模型生成较自由的 SQL,至少要做三件事:只授予只读账号;限制查询超时和返回行数;SQL 生成后用 EXPLAIN 做一轮前置校验,发现扫描行数过大就拒绝执行。Doris 的资源组(Workload Group)可以按账号或查询特征限流,这在大模型接入场景里几乎是必须配置的。
我在项目里踩过一个典型的坑:一开始给 Agent 用的账号是全库权限,结果测试阶段模型的随机探索把一个几百亿行的明细表全扫了一遍,集群 CPU 直接拉满。后来收紧账号权限、只开放聚合视图,并且把所有 Agent 查询都路由到独立 Workload Group,设置 30 秒超时和内存上限,之后再没出过事故。这是 AI 融合范式落地时最容易被忽略但又最重要的一课。
5. SelectDB:把三大范式变成开箱即用的云服务
5.1 为什么需要 SelectDB:开源很强大,但运维很现实
Apache Doris 功能很全,但生产环境要搞好,需要有人管部署、配置、监控、升级、容量规划。很多中小团队没有专职数仓运维,或者项目周期短,没有时间从零搭集群。SelectDB Cloud 做的事情就是把 Doris 变成全托管服务:底层存储与计算分离,计算组(Warehouse)可以独立扩缩容,存储用对象存储持久化,数据不随计算节点变化而丢失。
对 AI 团队来说,计算隔离是很实用的功能。可以把报表查询放在一个 Warehouse,AI 应用的高频查询放在另一个 Warehouse,两边互不干扰。流量高峰时让 AI 的 Warehouse 自动扩容,低峰自动缩容,成本跟随实际用量走。这套模型跟以往“买一台大机器”的思路完全不同,特别适合多团队共用一个集群的场景。
5.2 开源 Doris 与 SelectDB 选型对照
直接给对照表,方便决策。
| 对比维度 | 开源 Apache Doris | SelectDB Cloud |
|---|---|---|
| 部署运维 | 自建集群,需投入运维人力 | 云上托管,开箱即用 |
| 扩缩容 | 手工评估、扩容节点 | 计算组弹性伸缩,按需配置 |
| 存储模式 | 本地存储为主,可用对象存储 | 存算分离,存储随用随付 |
| 成本结构 | 固定机器成本 | 计算 + 存储按用量,需关注 CU 消耗 |
| 功能版本 | 跟随社区版本 | 商业版本,含企业级管理、技术支持 |
| 适用团队 | 有数仓/运维团队,数据不出域要求严格 | 团队小、周期紧、希望快速落地 |
5.3 低成本玩转 SelectDB 的几个建议
先说成本。SelectDB 按计算资源(CU/时长)和存储计费,最容易烧钱的做法是没限制地跑大查询。建议从一开始就设置查询超时、内存限制,把数仓账号按部门拆开,每个账号绑定额度。第二,AI 场景的并发查询模式和其他分析不一样,通常单条 SQL 很小、次数非常多,要按 QPS 估算计算组规格,而不是按数据量估算。第三,能用物化视图榨干重复计算就别让每一条查询都重新扫表。最后,如果只是验证想法,先用免费试用或小额配额把原型跑起来,确实稳定了再决定要不要整体搬迁。
6. 常见问题与排查技巧实录
6.1 实时导入的乱序与重复
最常被问的是 Unique Key 表导入重复数据怎么保证最终一致。Doris 提供了 Sequence Column 机制,可以在写入时指定某个字段作为版本号,比如业务系统自带的 event_time 或 update_time。同主键多条数据到达时,Doris 比较 Sequence Column 的大小,保留更大的一条。如果不用它,那么数据到达顺序就决定了最终结果,上游乱序时会出现旧数据覆盖新数据的问题。我强烈建议所有 Unique Key 表都配 Sequence Column,尤其是有 CDC 同步场景的。
ALTER TABLE user_profile ADD COLUMN version BIGINT KEY DEFAULT 0;然后导入时通过列映射指定 version 为数据里的事件时间戳。另外,实时导入最好用幂等的 Stream Load 或 Routine Load,配合导入标签(label)做去重,避免同一批数据因为网络重试被写入两次。
6.2 查询性能问题怎么定位
慢查询排查我一般按三步走。第一步看 Explain,确认扫描行数和 Join 顺序,最常见的问题是过滤条件没有下推到存储层,比如在 WHERE 里对列使用函数(WHERE DATE(ts) = '2024-01-01'),索引就失效了,改成ts >= '...' AND ts < '...'通常扫描量差一个数量级。第二步看 Profile,Doris 的 Profile 能展示每个算子耗时,可以快速定位是扫描慢、Join 慢还是网络传输慢。打开 Profile 的方式因版本而不同,一般是在 Session 里开启后执行查询,再到相关页面拉结果。第三步看 FE/BE 日志,确认是否有 Compaction 滞后或 Broker 负载异常。深翻页问题也要提醒:LIMIT 10000, 20这种写法会把前面一万行都扫描掉,建议用游标、分页键或者把翻页条件改成范围查询。
6.3 AI 场景里的几个专属坑
AI 集成的坑比传统分析更隐蔽。先说向量检索没生效。如果建了索引但查询计划里没走索引,多半是距离函数或向量维度跟索引定义不一致,或者是过滤条件太宽导致优化器认为全扫更快。其次,做余弦相似度前一定要保证 embedding 已经归一化,否则结果排序会失真。第三,RAG 检索时如果没有把业务过滤条件作为索引列,而只是在向量召回之后做内存过滤,数据量大了响应会劣化,正确做法是让结构化列出现在过滤条件下推里。第四,Text-to-SQL 的权限控制必须单独设,负责生成 SQL 的服务账号绝不能有 DDL 权限,也不要有访问元数据库的权限。最后,大模型连接数不大,但每次都可能触发全表扫描,所以务必让模型服务走独立 Workload Group。
写到这里,我自己也在心里把这三大范式过了一遍。说实话,最早接触 Apache Doris 时,我只当它是一个快一点的 OLAP 引擎;后来在数据湖接入和向量检索的实践里,逐渐意识到它在 AI 时代的角色已经变了,不再只是“给人看报表”的工具,而是整个数据体系的出口和底座。SelectDB 的价值则在于把这件事的门槛降下来,让没有专职数仓工程师的团队也能快速跑起来。
这几次经验让我最深的体会是:技术选型时可以追求新,但落地时一定要克制。凡是接入 AI 的查询都必须可控、有权限、有限流,宁可让模型多问一次,也不能让它把整个集群拖垮。AI 时代的数据分析,拼的往往不是某个单点功能有多炫,而是数据链路是否完整、口径是否统一、权限是否清晰。希望这篇整理能帮你少走一点弯路。