新鲜度、索引与按需运行

第 3 课 · 共 6 课 约 9 分钟

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

本课目标

读完这一课,你将能够

  • 说出从源头变化到读到它,时间花在哪几站
  • 分清「变了才做」和「有人看才算」,用 change 触发代替轮询
  • 判断一个定时该不该改成触发,说出定时的频率分级

延迟花在哪里

上一节讲了人的改动怎样叠在数据源上;这一节看数据有多新。这是一条链上的问题:示例工厂的 MES 里一个工单变了,要经过四站:发布端发现变化,推进数据流,同步绑定写进对象,最后才有人来读。前三站是写,最后一站是读。

  1. 01源头变化MES 里工单改了
  2. 02发布端发现源头推送,或旁边的便宜探针
  3. 03落账并同步按序落账,同一次发布里写进对象
  4. 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 条件怎么写;如果找不到这样的变化,说明时间本身是不是条件。

小测

选一个答案,马上看解析。

Q1「物料库存低于安全库存,就起草采购订单」最合适的触发方式是?

Q2一个订阅了工单的看板页面被切到后台超过 1 分钟,实时连接会怎样?

Q3「ERP 数据两小时没来就告警」为什么不能用 change 触发?

延伸阅读