新鲜度、索引与按需运行
数据流延迟主要在发布端;dataset 过期时读取触发同步,fresh 查询最多等 20 秒;健康状态看得见;有人看才算;数据一变才需要做的事用 change 触发,不轮询。

本课目标
读完这一课,你将能够
- 说出从源头变化到读到它,时间花在哪几站
- 分清「变了才做」和「有人看才算」,用
change触发代替轮询 - 判断一个定时该不该改成触发,说出定时的频率分级
延迟花在哪里
上一节讲了人的改动怎样叠在数据源上;这一节看数据有多新。这是一条链上的问题:示例工厂的 MES 里一个工单变了,要经过四站:发布端发现变化,推进数据流,同步绑定写进对象,最后才有人来读。前三站是写,最后一站是读。
- 01源头变化MES 里工单改了
- 02发布端发现源头推送,或旁边的便宜探针
- 03落账并同步按序落账,同一次发布里写进对象
- 04读到当前值直接读,没有另一份要重建
读的一侧没有「重建索引」这一步:对象的当前值在同步那一刻就合好了,搜索、聚合、SQL 直接读它。派生属性只在有人读的时候算,而且只算这一页要显示的对象;聚合也是有人问了才算。数据流链路的延迟主要取决于发布端多久发现变化。若 Object Type 由 dataset 的 TableImport 或下游加工供数,上游数据过期时,读取会触发按需同步。导入和连接须为 active,导入须在工作时段内;近期失败会暂缓重试。缺省先返回当前值、后台同步;?fresh=true(Python 的 fresh=True)最多等 20 秒,未完成则返回现有数据。
能推送的数据源,变化一发生就推;不能推送的,发布端在旁边做一个便宜的探针,比如数据库的变更跟踪或更新时间水位。一个探针服务所有订阅者,而不是每个看板各查一次。发布端只发变化的行,第一次发全量;对象越多、属性越多,这样省下的越多。数据流按序号落账,重试带幂等 id,不丢不重。
健康状态是看得见的
数据变旧有两种:真的没有变化,或者变化没进来。第二种更危险,所以每条流都带着发布端的健康状态:
- 在线或离线:发布端每 30 秒发一次心跳,90 秒收不到就是离线。
- 状态分正常、部分异常、连不上数据源、已停止。前三种由适配器自报;发布端 15 分钟收不到任何输出,就报部分异常。
- 距上次全量快照满 6 小时后,发布端在适配器下一次输出
rows时发全量。需要定期校准,适配器就要定期输出全表。
订阅端拿得到这些状态(onStatus 里的 publisher),应用可以据此提示「数据可能不是最新」。数据源断了,看得见,不会像定时任务的看板那样悄悄变旧。
aidc semantic streams status mes-work-orders # 发布端在线吗?正常吗?
aidc semantic datasource list # 每条绑定落后几批
aidc semantic streams tail mes-work-orders # 实时看变化,每次一行
有人看的时候才算
AIDC 的做法是「有人看才算」:不为没人看的东西付计算。和常见的轮询相比,差别是这样的:
| 定时轮询 | 按需加触发 | |
|---|---|---|
| 什么时候运行 | 每隔固定时间,不管有没有变化 | 数据变了,或者有人打开页面 |
| 没人看、没变化时 | 照样运行,照样占资源 | 什么都不发生,零成本 |
| 源系统的压力 | 每个看板各查一次 ERP | 只有一个发布端探针 |
实时订阅也守这条原则。对象集的 subscribe() 只在页面开着时保持一条连接:页面隐藏超过 1 分钟就自动断开,切回来带着序号续上;服务端每 4 分 40 秒左右收尾一次,SDK 自动续接。连接挂着的时间计入应用的计算分钟,缺省每月 2000,用完就不再重连。
const client = semantic.ontology();
const orders = client.objects("workOrder");
const view = orders.subscribe({
onChange({ object, state }) {
state === "REMOVED" ? drop(object.__primaryKey) : upsert(object);
},
async onOutOfDate() {
const page = await orders.fetchPage({ $orderBy: { dueAt: "asc" } });
render(page.data); // 首次订阅或数据失效时,重新读取当前页
},
onError({ subscriptionClosed, error }) {
if (subscriptionClosed) showOffline(error);
},
}, { properties: ["workOrderNo", "status", "dueAt"] });
// 页面用完就 view.unsubscribe()
触发优先于定时
想让某件事在数据变化后自动做,先问一句:是不是「数据一变才需要做」?是,就用工作流的 change 触发:对象一变就开跑,条件为假不开跑,没有变化时零成本。定时只留给时间本身就是条件的事。
示例工厂的「检验失败就建缺陷、暂停工单」是有变化才需要做的事,用 change,加 cooldown 防止告警风暴:
"trigger": {
"change": { "type": "inspection", "when": "=object.result == 'fail'", "cooldown": "30m",
"input": { "inspection_id": "=object.inspectionId" } }
}
「ERP 数据两小时没来就告警」不一样:数据不来,就没有变化可触发,只有时间本身能当条件,所以用定时:
"trigger": { "schedule": { "every": "2h" } }
定时有频率分级,部署时会检查:
- 少于 5 分钟:拒收,要实时就用
change。 - 5 分钟到 1 小时:高频,一定告警。每 5 分钟一次,每月就是 8,640 次。
- 1 小时到 1 天:中频,提示每月次数。
- 1 天及以上:正常。
下一节开始问问题:怎样把一个业务问题写成一个对象集。
要点
- 数据流延迟主要在发布端;dataset 上游过期时,读取触发同步,fresh 查询最多等 20 秒。
- 读的一侧没有重建索引:当前值在同步时就合好了,派生属性和聚合有人问才算。
- 每条流带发布端健康状态:心跳 30 秒一次,90 秒收不到为离线。
- 实时连接只在有人看时开:页面隐藏超过 1 分钟就断开,时长计入计算分钟(缺省每月 2000)。
- 数据一变才需要做,用
change触发;定时只给时间本身是条件的事,少于 5 分钟拒收。
练一练
把一个定时任务改成触发
从你们现在靠定时跑的东西里挑一个,或者用示例工厂的「物料低于安全库存」。
写出它应该在哪个对象类型的哪种变化上触发,when 条件怎么写;如果找不到这样的变化,说明时间本身是不是条件。
一个 every: "15m" 的定时,每月运行多少次?它落在频率分级的哪一档?会怎样提示?
对你们的一条数据流运行 aidc semantic streams status 和 aidc semantic datasource list:发布端在线吗?绑定落后几批?
小测
选一个答案,马上看解析。
Q1「物料库存低于安全库存,就起草采购订单」最合适的触发方式是?
只有库存一变才需要做,这正是 change 的用处;定时扫描会白跑,而且 5 分钟一次属于高频,会被告警。
Q2一个订阅了工单的看板页面被切到后台超过 1 分钟,实时连接会怎样?
连接只在有人看时开,这样没人看的时候不占计算分钟;切回来自动续上。
Q3「ERP 数据两小时没来就告警」为什么不能用 change 触发?
看门狗要发现的正是「没有变化」,所以只能用定时;频率也要遵守分级。