NoETL是这两年在数据圈子里讨论热度上升得很快的概念,但说实话,真正把它落到埋点数据场景里跑通、跑出价值的团队并没有那么多。我过去大半年干的一件事,就是把公司核心业务线的埋点数据链路从一套“能跑但没人敢碰”的传统ETL体系,改造成以语义编织为核心的NoETL范式。这篇文章不打算做概念科普,我想直接把这套改造背后踩过的坑、想明白的取舍、以及可复制的落地方案完整写出来。如果你是正在被海量埋点数据和日新月异的分析需求拖垮的DE或DS,或者团队正在纠结要不要引入NoETL,这篇文章应该能给你一个比较实在的参照系。
先说结论:我们并没有真的把ETL全部干掉,而是把80%到90%的ETL逻辑从“预先加工”阶段搬到了“查询时计算”阶段,用一套语义层把原始埋点字段、业务口径和计算逻辑编织在一起。这套改造落地之后,数据分析需求的平均交付时间从按周计变成了按小时计,DE团队从疲于改管道变成集中精力做数据资产治理,DS终于可以自己动手解决大部分取数问题而不是排队等排期。
1. 为什么埋点数据链路是最该先被NoETL改造的
如果你所在的公司还保持着“埋点数据先进数仓、再清洗成宽表、最后出指标”的经典流程,你一定体会过那种“明明数据每天都在采,却总是差一口饭”的感觉。埋点数据大概是所有数据资产里最反ETL的一类,这不是某一个环节做错了,而是它的天然属性和ETL流程的设计假设是冲突的。
1.1 传统ETL处理埋点数据的三重失效
我们先说第一重失效:量级。一个日活百万级别的App,每天上报的埋点事件量通常在上亿到几十亿条区间。传统ETL的经典路径是:Kafka接入日志后,经过清洗、去重、JSON解析、过滤无效事件、关联维度表、落宽表,再写一堆Hive SQL或Spark任务做聚合。链路又长又重,数据量上来以后,单次调度的运行时长、资源占用和失败重跑成本都会指数级上升。埋点数据是典型的只追加数据,只增不减,ETL任务越写越重,但下游消费方反而嫌数据不够及时。
第二重失效在于变化。有过埋点治理经验的人都懂,埋点schema变更是家常便饭。产品经理加一个埋点,前端工程师随手定义字段名,有的用snake_case,有的用camelCase,参数一会儿套在properties里,一会儿平铺在event对象下,更头疼的是同一个事件在不同版本里字段含义还变了。传统ETL最怕schema变更,因为上游字段一旦变化,清洗逻辑、join逻辑、宽表结构全都得跟着改,改完通常还要重刷历史数据。我在项目里观察到一个很无奈的现象:数据团队的大量工时不是花在“产出业务价值”上,而是花在“应对业务乱改埋点”上。这不是某一个团队的锅,而是ETL这种“先定结构再填数据”的方式根本扛不住高频变化。
第三重失效在口径。同一个“活跃用户”,运营部的定义是“近7天有启动行为的设备去重”,产品部认为是“登录且触发过任意核心事件的用户”,算法组又觉得“有完整会话切片的样本”才算。这些口径在传统模式下全部以SQL的形式散落在各个团队的ETL作业里,谁也说不清某个指标到底是怎么算出来的,为什么跨部门对不上数。埋点数据本身又是典型的“长尾需求”数据,业务每天都会长出新问题、新口径,而传统ETL流程天然适合“需求稳定、结构固定、口径可预期”的场景,两者正好相冲。
1.2 一个非常典型的“两周才能上一个指标”现场
举个我印象很深的例子。业务侧提了一个“新手引导完成率”的指标需求,涉及的埋点事件有first_start、guide_step_view、guide_step_finish,还要和用户基础属性表关联。传统流程走一遍是什么感觉呢?
业务提需求之后,数仓的同学先得去查现有表,看事件到底落在哪、字段名是什么,结果发现guide_step_finish里的step_id命名不规范,有的版本是step01,有的版本是1,清洗逻辑要先做一层映射;接着写宽表加工任务,跑增量数据,还要重刷历史;再接着开发聚合逻辑,联调、测试、上线调度,最后业务拿到数。整个流程走下来,一到两周是常态。如果指标上线后业务发现口径理解有出入,比如要按“用户”而不是“设备”去重,那又要重新走一遍。
这套流程里真正耗时间的不是“算一个数”,而是“为了算这个数把管道重新铺一遍”。NoETL改造之后,同样这个需求,埋点原始数据已经完整落在OLAP引擎的贴源层里,我只需要在语义层定义一个指标:完成引导的用户去重数除以启动引导的用户去重数,加上时间窗口和过滤条件。定义一次,DS自助一拉,几小时就能拿到数。这不是说我们的团队有多厉害,而是因为大部分分析需求的本质是“用新方式组合已有的事实字段”,而不是“再造一条新的事实流水线”。
1.3 埋点数据还有三个比“量大”更麻烦的隐蔽特征
除了量大、易变、口径乱,埋点数据还有几个隐蔽特征,传统ETL处理起来特别吃力。
一是高噪声。真实埋点数据里有大量调试埋点、重复上报、测试机产生的脏数据,还有恶意刷量。传统ETL的清洗逻辑一旦写死,误伤正常数据的事经常发生。
二是会话属性。用户行为是按会话组织的,一个用户一次启动会连续产生几十条事件,很多指标需要按会话切片计算,比如“会话平均时长”“单次启动的页面浏览数”。会话切分逻辑在ETL里做还是查询时做,复杂度差别很大。
三是用户标识的漂移。设备ID、用户ID、账号之间的映射关系是动态变化的,同一个用户换设备、卸载重装、登录登出,都会影响去重口径。这种动态关系在物理表里维护非常痛苦,放到语义层里反而更容易通过实体关系模型来管理。
这几重特征叠加起来,传统ETL在埋点数据面前的脆弱性就完全暴露了。这也是我为什么认为,如果团队要尝试NoETL,一定要先从埋点数据场景入手,因为在其他类型的数据上,NoETL和传统ETL的差距未必那么明显,但在埋点数据上,这个差距是碾压级的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 语义编织到底在织什么:NoETL的核心原理拆解
很多初次接触NoETL的人容易误解,以为NoETL就是不做数据清洗、不做数据加工,把原始日志扔给分析师去查。这是很危险的理解。NoETL真正做的事情,是把“搬到数仓”和“让数据能回答问题”两件事彻底解耦,把业务逻辑的沉淀位置从“数据管道”转移到“语义层”。
2.1 先分清“数据可查”和“数据可用”是两件完全不同的事
传统ETL的隐藏假设是:为了让数据可用,你必须先通过一系列加工动作,把原始数据变成某种接近业务形态的中间表或宽表,业务方只能在这个加工结果之上做查询。这个假设成立的前提是“业务需求可以被提前预测”。但埋点数据的现实是,业务需求根本预测不了,今天要渠道转化分析,明天要功能使用热度,后天又要用户旅程做分群,每一个需求的数据组合方式都不同。
NoETL换了一个思路:让数据可查的物理工作尽量简单,只做最基础的格式规整和分区优化;让数据可用的业务工作全部放到语义层,通过声明式的模型把原始字段翻译成业务语言。物理层追求极致的稳定性,语义层追求极致的灵活性。
这个分离在工程上的价值非常大。物理层的修改频率可以降到极低,因为底层就是一套多分区、多副本的明细数据,根本不需要频繁变更;语义层的修改则可以随时发生、随时生效。上游新增了一个埋点字段,传统ETL要改清洗逻辑、改宽表、改下游依赖,NoETL模式下只需要把新字段声明进语义层对应的维度或指标,新模型立刻就能覆盖到新字段。
我常用一个织布的类比来解释语义编织:原始埋点字段就好比无数根棉线,每根线单独看没有意义,但当你按照经线和纬线的逻辑把它们编织在一起,就形成了一块有结构的布。语义编织里的“经线”是事件和实体,“纬线”是维度和指标,而编织的规则就是业务口径。
2.2 语义层到底在编什么:字段、定义、计算逻辑三者合一
语义编织的核心动作不是给字段改个名,而是把三样东西严格缝合在一起。
第一是原始字段,也就是埋点事件里的event、params、timestamp、user_id、device_id、properties等等。第二是业务定义,比如“完成新手引导”“活跃用户”“核心功能渗透率”这些业务概念。第三是计算逻辑,包括去重口径、时间窗口、分子分母、过滤条件、关联关系。
我在项目里把这套模型抽象成四类对象:事件、实体、维度、指标。事件对应埋点日志里的每一次用户行为;实体指用户、设备、账号这类业务主体,它们之间有动态的映射关系;维度是观察数据的切面,比如版本号、渠道、操作系统;指标是挂着计算公式的业务口径。语义编织,就是把这四类对象用声明式的方式网格化地连接起来,形成一张可复用、可组合、可解释的语义网络。
给你看一个具体的例子。假设一条原始埋点数据长这样。
json复制{
"event": "guide_step_finish",
"ts": 1710880000000,
"user_id": "u123",
"device_id": "d456",
"properties": {
"step_id": "step_2",
"guide_version": "v3",
"duration_ms": 3200
}
}
在这条数据上,语义层可以给出如下声明:
- 实体:用户(user),主键为user_id;设备(device),主键为device_id,同时声明user和device之间的映射关系。
- 事件:guide_step_finish,归入“新手引导”业务流,属性包含step_id、guide_version、duration_ms。
- 维度:guide_version,枚举有v1、v2、v3;step_id,枚举有step_1到step_4。
- 指标:近30天新手引导完成率,分子是触发过final_step且属于引导流程的去重用户数,分母是触发过first_start的去重用户数。
这条链路对业务是完全透明的。分析师看到的是“近30天新手引导完成率”这样一个业务指标,他不需要关心底层表结构是什么样的,不需要知道step_id做过什么映射,更不需要去翻原始日志。整个口径从业务定义到物理字段的映射,全部在语义网络里可以追溯。
2.3 它和“数据虚拟化”“指标中台”到底有什么区别
每次聊NoETL都会有人问,这跟数据虚拟化、指标中台、Headless BI有什么区别。我的看法是:它们有重叠,但不是一回事,NoETL更像一种贯穿始终的原则,而不是某个具体的工具类型。
区别可以从三个维度看。
| 维度 | 数据虚拟化 | 指标中台 | NoETL语义编织 |
|---|---|---|---|
| 侧重方向 | 跨源查询、逻辑视图 | 指标统一管理和服务 | 语义建模 + 查询时计算 |
| 是否做物理加工 | 一般不做 | 通常会做部分预聚合 | 只做最小物理规整 |
| 核心产物 | 虚拟表 / 逻辑视图 | 指标目录 / API服务 | 语义网络 / 可组合的指标模型 |
| 对查询引擎的要求 | 中等 | 中等偏高 | 很高 |
数据虚拟化讲究的是把不同数据源统一到一个查询入口,它解决的是“连接”问题。指标中台讲究的是把核心指标统一管理起来,用一套服务对外提供口径一致的指标值,它解决的是“统一输出”问题。而NoETL语义编织强调的是“语义计算发生在查询阶段”这个原则,它可以把数据虚拟化作为实现手段,也可以把指标中台作为对外服务形态。真正让它区别于传统模式的地方,是业务逻辑的沉淀位置从管道变成了模型。
所以我在内部沟通时很少单独说NoETL,而是说“我们要从管道驱动转向语义驱动”。这个表述更准确,也更容易让团队理解改造的方向。
3. 一套可复制的落地路径:从贴源层到指标语义层的搭建过程
概念讲清楚之后,最容易被问到的就是:这套东西到底怎么落地?技术栈怎么选?数据怎么组织?团队怎么分工?我把自己亲测可行的路径拆开来讲,不一定适用于所有公司,但应该能给大部分埋点场景提供一个比较完整的最小方案。
3.1 技术栈选型:OLAP引擎和语义层工具怎么选
NoETL对底层查询性能的要求非常高,因为语义层里的指标是在查询时实时计算的。如果底层引擎不过关,一个稍复杂的指标查询跑个几分钟,业务立刻反弹。我们当时对比了ClickHouse、Apache Doris和StarRocks,最后选了Apache Doris。
选Doris的最核心原因是它在明细查询和多表join上的综合表现比ClickHouse更稳。埋点数据要频繁join维表、用户属性表、ID映射表,ClickHouse在超大表join场景下要非常小心,而Doris在这方面相对省心。其次是Doris的物化视图、倒排索引在事件名检索和自动聚合场景下很有用。第三是运维成本,Doris的扩缩容和副本管理比ClickHouse的集群运维门槛低不少,Doris支持标准MySQL协议,DS的学习成本几乎为零。
如果你所在团队已经在用某个引擎,我的建议是不要轻易迁移,因为NoETL改造的难点从来不在引擎本身。下面这张表是我做选型时整理的,供参考。
| 引擎 | 擅长的场景 | NoETL场景下的注意点 |
|---|---|---|
| Apache Doris | 明细 + join + 点查均衡 | 最适合埋点明细数据的NoETL化 |
| ClickHouse | 大宽表聚合极快 | join要谨慎,语义层多join会吃力 |
| StarRocks | Doris衍生版,性能更强 | 如果已在用,直接沿用,不必切换 |
语义层工具方面,我们走了一条“先自研、后开源”的路,因为当时需要支持中文业务口径的审批流和复杂的权限矩阵,开源工具没有完全匹配的。但我的建议是,中小团队不要一上来就自研,先用Lightdash、Cube或者dbt semantic layer这类开源方案把流程跑通,验证语义模型的设计逻辑,后面发现瓶颈再逐步替换自研。语义层本质上是一个“元数据 + 计算模板 + 权限控制”的组合,工具不是关键,语义模型的严谨程度才是关键。
3.2 贴源层:只做物理规整,不做业务加工
这是整个NoETL架构里最容易做错的一层。有人觉得NoETL就是不做ETL,于是把原始日志直接丢给分析师,让分析师自己去解析JSON、自己清洗、自己过滤,这是从一个极端走到另一个极端。
我理解的贴源层要做的是“物理规整,而非业务加工”。具体包括四件事:
- 埋点日志原样落地,按时间分区写入OLAP引擎,不做业务字段的过滤和裁剪。
- 做必要的格式规整,比如JSON字段展平、非法类型转null、时间戳统一成毫秒、公共属性抽取。
- 完整保留原始事件字典,包括event_name、schema版本、上报方、上报版本号这些元信息,这是后续语义建模的底账。
- 同时保留日级和小时级两种物理分区粒度,满足不同时效性需求的查询。
贴源层的核心原则是“什么都不知道,什么都不丢”。它不知道一个字段是运营渠道还是产品版本,它只知道把这些数据完整、高效、低成本地存下来。我见过不少团队在这个层面就开始建宽表、打标签,结果改造半天还是走ETL的老路,只是把ETL往前提了一步。
对了,这里还有一条必须守住的技术底线:原始埋点数据绝不能只存在于清洗后的形态里。无论你后续做不做NoETL,原始数据都要有独立完整的归档,这是数据资产的最后一道保险。
3.3 语义层设计:事件、实体、维度、指标四类对象怎么建模
贴源层做扎实之后,重头戏就是语义层建模。我建议不要一上来就堆指标,而是先按四类对象把骨架搭起来。
第一步是实体建模。明确业务关注的主体是什么,通常是用户、设备、账号。实体建模要解决的问题是ID映射,也就是说同一个业务主体可能有多个ID标识,这些ID之间的映射关系要有明确的维护方式。我们在语义层里维护了一张实体映射表,每次查询时自动按映射关系做去重,而不是在物理层写死。
第二步是事件建模。把零散的埋点事件归并成业务事件流,确认每个事件归属哪个业务流程、具备哪些有效属性、哪些事件是关键事件。这一步需要产品经理参与,因为只有业务侧才清楚事件之间的流程关系。
第三步是维度建模。统一事件属性的枚举值,把step_01、step1、第一步这类不同写法的值映射为统一的枚举。维度建模特别考验耐心,因为埋点数据的枚举值混乱程度远超你的想象,一个版本字段可能有十几种写法。
第四步才是指标建模。指标定义需要包含指标名、业务口径、计算公式、owner、生效时间、频率和维度限定。下面是我们在内部指标定义时用的一种近似YAML结构。
yaml复制metric:
name: new_user_next_day_activation_rate
display_name: 新用户次日行为激活率
owner: data_analytics
definition:
numerator:
event: [first_start, any_core_event]
window: [day1, day3]
distinct: user_id
filter: user.is_new = true
denominator:
event: first_start
window: day0
distinct: user_id
filter: user.is_new = true
dimensions: [channel, app_version, os]
cron: daily
这种声明式的定义有几个好处:一是口径可读,任何新同事一看就懂;二是口径可审计,所有指标都有owner和生效周期;三是口径可复用,一个指标定义后,下游所有报表、看板、分析查询都引用同一个定义,不会再出现多个团队各写各的SQL导致口径漂移。
3.4 一个完整案例:从“新用户次日行为激活率”从需求到自助查询
我再用一个真实案例把全流程串起来。业务提出要关注新用户激活情况,核心指标是“新用户次日行为激活率”。传统做法需要数仓开发一个调度任务,把新用户、次日行为、核心事件多次join后计算出来。我们现在的流程是这样走的。
第一步,确认贴源层已经包含first_start、any_core_event等事件数据,且用户属性数据已经通过实体映射接入语义层。
第二步,在语义层定义上述YAML结构中的指标,并指定可下钻维度为channel、app_version、os。
第三步,DS自助在查询界面上选择指标和维度,系统自动翻译成底层查询。实际产生的SQL大致是扫描贴源层明细,按照语义模型做窗口过滤和去重计算。
第四步,指标自动进入指标目录,后续所有报表引用该指标时都指向同一个定义。
整个流程下来,从需求提出到业务看到数据,压到了半天以内。而且后续任何口径变更,比如把“次日”改成“3日内”,只需要修改语义定义并重新发布,所有下游自动生效。这在传统ETL模式里是不可想象的。
4. 激活之后的变化:DE和DS的新分工,以及四个必须先解决的问题
NoETL语义编织落地之后,团队的工作方式会发生肉眼可见的变化。但我想强调的是,转变不仅仅是技术栈的变化,更是数据团队角色定位的变化。如果你问为什么现在越来越多团队在讨论DE和DS这两个角色的边界,我的答案是:因为NoETL一类的范式正在让DE从“管道维护者”变成“数据资产架构师”,同时也推着DS从“等待取数”变成“自助建模分析师”。
4.1 DE不再修管道,而是成了数据资产的架构师
传统模式下DE最日常的工作就是改ETL作业、调调度依赖、排查数据延迟,大量时间消耗在维护链路上。NoETL改造后,这条链路大幅简化,DE的精力开始转向更重要的事情。
第一个重心是埋点规范的治理。DE要和产品、前端一起制定统一的埋点命名规范、字段类型规范、公共属性规范,这是语义层稳定性的根基。第二个重心是语义模型的设计和评审。一个指标定义得好不好,直接决定了后续所有分析是否顺畅,这要求DE从纯技术视角转向业务视角。第三个重心是物理层性能优化。查询时计算模式把压力全部转给了OLAP引擎,所以DE要持续做分区分桶优化、物化视图策略、查询慢SQL治理。
我个人的体会是,这个转变对DE的综合素质要求明显提高了,但工作成就感也强了很多。从每天跟调度告警搏斗,变成设计一套让整个团队用起来顺畅的数据资产,这个变化还是很值得的。
4.2 DS不用再排队等数,但要学会建模思维
DS的变化就更直接了。以前DS的需求链路是“提需求、排期、等开发、测试、交付”,一个简单分析问答题都要等好几天。现在大多数常规分析DS可以自己在语义层上完成,指标组合、维度下钻、时间窗口调整,全部自助,一个上午能做完以前一周的取数工作量。
但这里有一个隐藏门槛:DS需要学会语义建模的思考方式。他不能只想着“我要一张表”,而要想清楚“我关心什么实体、什么事件、什么指标、什么维度”。如果DS没有建模意识,就会在语义层上定义出大量口径打架的临时指标,反而把语义层搞乱。所以我们在改造的同时安排了几轮面向DS的语义建模培训,效果比预想好得多,第一批掌握建模思维的DS很快就成了团队里的分析骨干。
4.3 想复制这套范式,先回答四个问题
NoETL语义编织确实好,但它不是万能药。公司想复制这套范式,我建议先老老实实回答下面四个问题。
第一,底层OLAP引擎能不能支撑查询时计算?如果一张明细表几亿行、一个指标查询要几十秒才能出结果,那语义层做得再好也没用。查询性能是NoETL的生命线,这一点卡不住,后面全是空中楼阁。
第二,埋点数据的基础质量有没有底线?语义编织只是把数据翻译成业务语言,它不会自动清洗垃圾数据。如果埋点命名混乱、上游字段随意变更、事件上报经常缺失,那语义层再强也是基于是垃圾的垃圾。我见过一个团队硬上NoETL,结果因为上游埋点质量太差,每个语义指标都在跟脏数据搏斗,最后不得不回头重新治理埋点。
第三,业务方和数据团队之间有没有“指标Owner”机制?口径不一致的根源是没有人对“什么叫活跃用户”负责。必须有一个人或一个小组对每个核心指标的定义有最终解释权,否则语义层里的指标定义也会像以前的SQL一样四分五裂。
第四,团队愿不愿意改变工作习惯?DE敢不敢放手让DS自助?DS愿不愿意学建模?这里面的阻力往往比技术难度更大。我在改造初期遇到的最大障碍不是技术选型,而是有经验的DE习惯了“别人提需求我开发”的模式,对“把开发工作交给DS”这件事很不安。这个过程需要耐心,也需要管理层明确支持。
4.4 改造过程中的几条实操提醒
最后分享几条实操层面的提醒,都是踩过坑换来的。
不要一开始就全量改造。选一条核心业务线的埋点链路做两到三个月的试点,跑通了再逐步推广。试点阶段要重点验证三件事:查询性能能否扛住,语义模型是否稳定,业务侧是否真的能自助用起来。
语义层的版本管理很重要。指标发布要像发版一样有评审、有记录、有生效时间。我们内部用了一套指标审批流,新指标必须定义owner、口径说明和适用场景,否则不允许上线。
物理层的“原始数据不丢”是底线。语义层可以随意调整,但贴源层的原始数据必须完整保留。这是所有上层灵活性的基础,也是出问题时的逃生通道。
查询性能必须写进验收标准。每次语义层发布新指标,都要做性能压测,出数超过阈值的指标不予上线。宁可少一个口径复杂的指标,也不能让整体查询体验崩掉。
高频指标可以做物化。平衡性能的手段不是回到批量加工,而是在语义层下面自动维护一批物化结果。物化不是ETL,它只是把查询结果缓存起来,源头的语义模型依然是唯一的业务口径定义。
做了这轮改造之后,我最深的感受是:NoETL语义编织真正激活的不只是海量的埋点数据,还激活了数据团队本身。DE从重复劳动里解放出来,去做更靠近业务的数据架构设计;DS从取数困境里解放出来,把精力还给真正的分析思考。这套模式当然有它的适用边界,技术底子薄、埋点基础差、团队意愿低的公司硬推大概率会失败,但只要你把地基打牢,一步一个脚印地做试点,它给数据工程带来的效率提升是传统ETL体系很难追上的。
