Apache Griffin 数据质量实战详解

基于已完成部署的 Apache Griffin 服务器开展数据质量实战:REST API 认知、创建 accuracy 度量与 quartz 调度作业、官方示例 predicate 路径不符导致作业卡 NOT_FOUND 的完整排障链、Spark on yarn 端到端执行核验,以及 ES 指标与 metrics API 的结果核验(125000 行、miss 502、准确率 99.5984%)。

Apache GriffinApacheGriffin大数据Spark数据质量2026/10/15

Apache Griffin 数据质量实战详解

目标:在已完成部署的 Apache Griffin 服务器上,从零完成一次端到端的数据质量检测实战——通过 REST API 创建 accuracy(准确率)度量与 quartz 调度作业,解决官方示例数据就绪路径与真实数据布局不符导致作业卡 NOT_FOUND 的经典故障,最终在 Elasticsearch 与 metrics API 中核验质量指标。全部请求、响应与日志均来自真实环境实测(准确率指标 99.5984%)。

一、环境信息

1.1 服务器与组件版本

本篇承接本专栏《Apache Griffin Docker Compose 部署指南》完成部署的 Griffin 服务器(batch 形态),将其视为一台"已部署好 Griffin 的数据服务器",所有操作均通过服务端口进行。实测组件版本:

  • Griffin Service:0.2.0(GET /api/v1/version 实测返回;部署套件组件标记为 0.3.0)
  • Hadoop:2.6.5(服务器内安装目录 /apache/hadoop-2.6.5,默认文件系统 hdfs://griffin:9000)
  • Spark:standalone master/worker(spark://griffin:7077),度量作业经 Livy 以 yarn-cluster 模式提交
  • Livy:8998 端口(REST 作业提交通道)
  • Elasticsearch:5.5.2(指标存储)
  • PostgreSQL:9.3(quartz 调度元数据库);MySQL:5.x(套件内置附加数据库)
  • Hive metastore:thrift 9083(被检表元数据)

1.2 服务地址与端口

本篇所有命令均在这台服务器上以可独立执行的 curl 形态给出;从其他机器访问时,把 localhost 替换为该服务器 IP 即可:

服务地址用途
Griffin Service(REST API + UI)http://localhost:38080度量/作业/指标管理与查询
Elasticsearchhttp://localhost:39200指标检索
Livyhttp://localhost:38998Spark 批作业状态查询
YARN ResourceManagerhttp://localhost:38088Spark on yarn 资源与作业页面
Hive metastorelocalhost:39083被检表元数据(thrift)

环境就绪的实测核验:

curl http://localhost:38080/api/v1/version
0.2.0

1.3 demo 数据分布

部署套件启动时自动生成了两组 demo Hive 表(经元数据 API 实测确认存在):

curl "http://localhost:38080/api/v1/metadata/hive/tables/names?db=default"
["demo_src","demo_tgt"]

数据的真实 HDFS 布局(本篇排障的关键事实,实测探明):表目录位于 /griffin/data/batch/demo_src 与 /griffin/data/batch/demo_tgt,按天 + 小时两级分区(实测含 dt=20261014/hour=14、hour=15、hour=16),每个 hour 分区下有数据文件与 _DONE 就绪标志文件,单文件约 1.36MB、共 125000 行,其中预置约 0.4% 缺失(502 行)。

二、Service REST API 认知

2.1 核心 API 总览

动手前先认知 Griffin Service 的 REST 面。实测可用的核心 API 如下:

API方法用途本篇使用
/api/v1/versionGET服务版本环境核验
/api/v1/measuresGET/POST/PUT度量定义的查询/创建/更新三、六
/api/v1/measures/{id}GET单个度量详情三
/api/v1/jobsGET/POST作业查询/创建四、六
/api/v1/jobs/{id}DELETE删除作业六
/api/v1/jobs/instances?jobId=...GET作业执行实例列表四、六
/api/v1/jobs/healthGET作业健康统计五、七
/api/v1/metricsGET指标聚合查询七
/api/v1/metadata/hive/dbs、/api/v1/metadata/hive/tables/namesGETHive 库表元数据一、五

其余服务面(Livy、ES、YARN)的实测核验:

curl http://localhost:38998/batches
{"from":0,"total":0,"sessions":[]}
curl http://localhost:39200/_cluster/health?pretty
{"cluster_name":"elasticsearch","status":"green","number_of_nodes":1,...}

(输出节选,HTTP 200;初始无任何 Spark 作业与指标索引,是干净的起点。)

⚠️ 注意:API 能力与版本强相关。实测 0.2.0 服务上,POST /api/v1/jobs/trigger/10(按 id 手动触发,0.6.0 官方接口文档与 postman 集合中存在)返回 404——该版本没有此端点,作业触发只能依赖 cron 周期调度。动手前务必先用 GET /api/v1/version 确认版本,再决定可用 API。

2.2 度量数据源与元数据接入

度量定义通过 HIVE 类型连接器读取 Hive 表,其 where 条件按分区模板过滤(如 dt=#YYYYMMdd# AND hour=#HH#),分区占位符在执行时由数据时间戳代入。库表可查性实测:

curl http://localhost:38080/api/v1/metadata/hive/dbs
["default"]

default 库与 1.3 节的 demo_src、demo_tgt 两表即为本次实战的被检对象:demo_src 为源表、demo_tgt 为目标表,准确率规则将逐行比对两表。

三、创建 accuracy 度量(Measure)

3.1 技术拓展:DQ 度量类型

💡 技术拓展:Griffin 的度量类型(dq.type)不止准确率一种,常见取值横向对比——

类型dq.type度量内容典型输出
准确率ACCURACY源表与目标表按规则比对的差异行total/miss/matched
完整性COMPLETENESS字段空值与缺失比例按列缺失统计
数据画像PROFILING各列分布统计(最大/最小/均值/去重数)列级统计值
时效性TIMELINESS数据到达延迟(流场景)延迟分布

本文实测采用 ACCURACY:它是官方示例的默认类型、指标语义最直观,且在本环境的端到端链路中完整跑通,适合作为部署后的第一个质量度量。

3.2 请求体与创建

度量定义是一个 JSON 对象,包含:两路数据源(source/target,各挂一个 HIVE 连接器)、逐行比对规则、以及数据就绪谓词。将以下官方示例请求体保存为 /root/measure-accuracy.json(原文照抄,其中 root.path 为官方 postman 集合原值,第五章将排障该路径):

{
    "name":"accuracy_measure",
    "measure.type":"griffin",
    "dq.type":"ACCURACY",
    "process.type":"BATCH",
    "owner":"test",
    "description":"measure created via REST API experiment",
    "data.sources":[
        {
            "name":"source",
            "connectors":[
                {
                    "name":"connector_name_source",
                    "type":"HIVE",
                    "version":"1.2",
                    "data.unit":"1hour",
                    "data.time.zone":"UTC(WET,GMT)",
                    "config":{
                        "database":"default",
                        "table.name":"demo_src",
                        "where":"dt=#YYYYMMdd# AND hour=#HH#"
                    },
                    "predicates":[
                        {
                            "type":"file.exist",
                            "config":{
                                "root.path":"hdfs:///griffin/demo_src",
                                "path":"/dt=#YYYYMMdd#/hour=#HH#/_DONE"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "name":"target",
            "connectors":[
                {
                    "name":"connector_name_target",
                    "type":"HIVE",
                    "version":"1.2",
                    "data.time.zone":"UTC(WET,GMT)",
                    "data.unit":"1hour",
                    "config":{
                        "database":"default",
                        "table.name":"demo_tgt",
                        "where":"dt=#YYYYMMdd# AND hour=#HH#"
                    },
                    "predicates":[
                        {
                            "type":"file.exist",
                            "config":{
                                "root.path":"hdfs:///griffin/demo_src",
                                "path":"/dt=#YYYYMMdd#/hour=#HH#/_DONE"
                            }
                        }
                    ]
                }
            ]
        }
    ],
    "evaluate.rule":{
        "rules":[
            {
                "dsl.type":"griffin-dsl",
                "dq.type":"ACCURACY",
                "name":"accuracy",
                "rule":"source.desc=target.desc"
            }
        ]
    }
}

创建度量(POST,返回 201 即成功):

curl -s -w "\nHTTP_CODE=%{http_code}\n" -X POST http://localhost:38080/api/v1/measures \
  -H "Content-Type: application/json" \
  -d @/root/measure-accuracy.json

响应(输出节选,实测):

{"measure.type":"griffin","id":1,"name":"accuracy_measure","owner":"test",...,
 "sinks":["ELASTICSEARCH","HDFS"],
 "evaluate.rule":{"id":2,"rules":[{"id":3,"rule":"source.desc=target.desc","dsl.type":"griffin-dsl","dq.type":"ACCURACY"}]}}
HTTP_CODE=201

逐项解读:id=1 是服务端分配的度量 ID(后续作业按它引用);sinks 显示服务端自动挂载了 ELASTICSEARCH 与 HDFS 两个指标存储(指标将同时写入 ES 索引与 HDFS 备份);evaluate.rule 中的比对规则 source.desc=target.desc 表示逐行比较两表的 desc 字段。

3.3 创建结果核验

curl -s -o /dev/null -w "%{http_code}" http://localhost:38080/api/v1/measures/1
200

(HTTP 200,返回完整度量 JSON,含 id=1、name=accuracy_measure 与两个 sink 配置。)度量定义此时只是"静态规则",尚不会有任何计算发生——它需要被下一章的作业调度起来。

四、创建调度作业(Job)

4.1 quartz 调度与 predicate 机制

作业(Job)把度量变成"按周期执行的调度任务",两个机制必须理解:

  • quartz 调度:作业创建后在服务端注册 quartz 任务(实测返回 quartz.group=BA),按 cron.expression 与 cron.time.zone 周期触发,调度元数据落在 PostgreSQL 的 quartz 库;
  • 数据就绪检查(predicate):每轮触发先执行 file.exist 谓词——按分区模板拼出 _DONE 标志文件路径(如 /dt=#YYYYMMdd#/hour=#HH#/_DONE)探测 HDFS,检查节奏由 predicate.config 控制(实测示例:每 1 分钟查一次、最多重复 2 次);未就绪则本轮执行实例标记为 NOT_FOUND 等待下一轮,不会空跑 Spark 作业。

4.2 请求体与创建

将以下作业请求体保存为 /root/accuracy-job.json(在官方 postman 结构上落本环境实测值:引用度量 measure.id=1,作业名 accuracy_job_exp,每 4 分钟调度一次,数据分段取前 1 小时):

{
    "measure.id": 1,
    "job.name": "accuracy_job_exp",
    "job.type": "batch",
    "cron.expression": "0 0/4 * * * ?",
    "cron.time.zone": "GMT+8:00",
    "predicate.config": {
        "checkdonefile.schedule": {
            "interval": "1m",
            "repeat": 2
        }
    },
    "data.segments": [
        {
            "data.connector.name": "connector_name_source",
            "as.baseline": true,
            "segment.range": {
                "begin": "-1h",
                "length": "1h"
            }
        },
        {
            "data.connector.name": "connector_name_target",
            "segment.range": {
                "begin": "-1h",
                "length": "1h"
            }
        }
    ]
}

创建作业:

curl -s -w "\nHTTP_CODE=%{http_code}\n" -X POST http://localhost:38080/api/v1/jobs \
  -H "Content-Type: application/json" \
  -d @/root/accuracy-job.json

响应(输出节选,实测):

{"id":10,"quartz.name":"accuracy_job_exp_1791990423152","quartz.group":"BA",...}
HTTP_CODE=201

id=10 为作业 ID,quartz.name 带毫秒后缀是 quartz 注册名。

4.3 执行实例观察(故障出现)

作业到点自动触发。查询执行实例:

curl "http://localhost:38080/api/v1/jobs/instances?jobId=10&page=0&size=10"

实测状态变化(输出节选):实例 {"id":15,"state":"FINDING",...} 创建后进入 FINDING(寻找数据阶段);约 2 分钟后再次查询变为 {"state":"NOT_FOUND"}。同时 Livy 侧始终没有提交任何 Spark 作业:

curl http://localhost:38998/batches
{"from":0,"total":0,"sessions":[]}

作业健康统计也显示作业不健康:

curl http://localhost:38080/api/v1/jobs/health
{"healthyJobCount":0,"jobCount":1}

(HTTP 200。)现象明确:作业能创建、能到点触发,但每轮都卡在"数据就绪检查"上,Spark 从未执行。下一章完整排障。

五、排障:predicate 路径不符导致作业卡 NOT_FOUND

5.1 故障现象与日志特征

作业实例状态 FINDING → NOT_FOUND 循环、Livy 无作业提交。查看服务日志(/root/service/service.log),每一轮调度都有相同特征行(实测原文):

2026-10-14 15:10:00.116  INFO [ryBean_Worker-4] o.a.g.c.j.FileExistPredicator : Predicate path: hdfs:///griffin/demo_src/dt=20261014/hour=14/_DONE
2026-10-14 15:10:00.122  INFO [ryBean_Worker-4] o.a.g.c.j.FileExistPredicator : Predicate path: hdfs:///griffin/demo_src/dt=20261014/hour=14/_DONE doesn't exist.

日志特征即 Predicate path: ... doesn't exist.——就绪谓词在 HDFS 上找不到 _DONE 文件。

5.2 定位过程(两步假设验证)

假设一:路径缺 authority(hdfs:///)导致解析失败。 把度量的 root.path 改为带 authority 的 hdfs://griffin:9000/griffin/demo_src 后 PUT 更新度量(HTTP 200),删除旧作业重建(新作业 id=25),predicate 仍然报 doesn't exist(service.log 15:16:00.071)。假设一被证伪——同时也说明无 authority 的 URI 在 0.2.0 上可被默认文件系统补全,不是根因。

假设二(决定性实验):_DONE 文件真的不存在吗?文件到底在哪? 在 Griffin 服务器上,用其内置 Hadoop 2.6.5(/apache/hadoop-2.6.5)的 classpath 运行独立 Java 探测程序,直接调用 Hadoop FileSystem.isFile(...) 对比两组路径,实测输出:

fs class: org.apache.hadoop.hdfs.DistributedFileSystem
fs uri: hdfs://griffin:9000
isFile(hdfs://griffin:9000/griffin/demo_src/dt=20261014/hour=14/_DONE): false
isFile(hdfs://griffin:9000/griffin/data/batch/demo_src/dt=20261014/hour=14/_DONE): true
isFile(hdfs://griffin:9000/griffin/data/batch/demo_tgt/dt=20261014/hour=14/_DONE): true

结论一目了然:/griffin/demo_src 下确实没有 _DONE;真实文件在 /griffin/data/batch/demo_src(与 demo_tgt)之下。再用元数据接口核对 demo_src 表的存储位置(location),实测为 hdfs://griffin:9000/griffin/data/batch/demo_src——Hive 表 location 才是数据的真实家。

5.3 根因结论

根因:官方 postman 示例的 predicate root.path 写的是 hdfs:///griffin/demo_src,比真实数据布局少了 data/batch 段(真实位置 /griffin/data/batch/demo_src),与套件内置 demo 数据的 HDFS 布局不符,导致 FileExistPredicator 永远返回 false,作业永远 NOT_FOUND。 补充两点实测佐证:其一,服务日志中有 Setting fs.defaultFS:hdfs://griffin:9000,谓词使用的文件系统单例由该默认值初始化,所以无 authority 的 hdfs:/// 前缀本身可正常补全;其二,对服务 jar 反编译(javap)确认 FSUtil.isFileExist 的真实逻辑是 fileSystem.isFile(path) || fileSystem.isDirectory(path),路径正确时文件与目录任一存在即通过。

⚠️ 注意:这是"能部署、跑不出指标"的最典型故障。排查口诀:先看 service.log 的 Predicate path: ... doesn't exist. 行拿到被探测的完整路径,再对比 Hive 表 location(元数据接口)确认真实数据目录,两者差在哪一段,root.path 就补哪一段。

六、修复度量并端到端跑通

6.1 修正 predicate 路径

按根因修正度量定义:source 连接器的 root.path 改为 hdfs://griffin:9000/griffin/data/batch/demo_src,target 改为 hdfs://griffin:9000/griffin/data/batch/demo_tgt,其余不变。将修正后的完整请求体保存为 /root/measure-accuracy-fix2.json(与 3.2 节请求体的区别仅在两处 root.path,以及末尾新增的 "id": 1 表示更新对象):

{
  "name": "accuracy_measure",
  "measure.type": "griffin",
  "dq.type": "ACCURACY",
  "process.type": "BATCH",
  "owner": "test",
  "description": "measure created via REST API experiment",
  "data.sources": [
    {
      "name": "source",
      "connectors": [
        {
          "name": "connector_name_source",
          "type": "HIVE",
          "version": "1.2",
          "data.unit": "1hour",
          "data.time.zone": "UTC(WET,GMT)",
          "config": {
            "database": "default",
            "table.name": "demo_src",
            "where": "dt=#YYYYMMdd# AND hour=#HH#"
          },
          "predicates": [
            {
              "type": "file.exist",
              "config": {
                "root.path": "hdfs://griffin:9000/griffin/data/batch/demo_src",
                "path": "/dt=#YYYYMMdd#/hour=#HH#/_DONE"
              }
            }
          ]
        }
      ]
    },
    {
      "name": "target",
      "connectors": [
        {
          "name": "connector_name_target",
          "type": "HIVE",
          "version": "1.2",
          "data.unit": "1hour",
          "data.time.zone": "UTC(WET,GMT)",
          "config": {
            "database": "default",
            "table.name": "demo_tgt",
            "where": "dt=#YYYYMMdd# AND hour=#HH#"
          },
          "predicates": [
            {
              "type": "file.exist",
              "config": {
                "root.path": "hdfs://griffin:9000/griffin/data/batch/demo_tgt",
                "path": "/dt=#YYYYMMdd#/hour=#HH#/_DONE"
              }
            }
          ]
        }
      ]
    }
  ],
  "evaluate.rule": {
    "rules": [
      {
        "dsl.type": "griffin-dsl",
        "dq.type": "ACCURACY",
        "name": "accuracy",
        "rule": "source.desc=target.desc"
      }
    ]
  },
  "id": 1
}

PUT 更新度量:

curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:38080/api/v1/measures \
  -H "Content-Type: application/json" \
  -d @/root/measure-accuracy-fix2.json
200

(HTTP 200。注意:PUT 更新后度量内的 connector/predicate 子对象 ID 会被服务端重建——实测连接器 ID 由 5/8 变为 35/38,属正常行为,引用度量时始终用顶层 measure.id=1。)

6.2 重建作业

先删除旧作业(含假设一阶段重建的 id=25):

curl -s -o /dev/null -w "%{http_code}" -X DELETE http://localhost:38080/api/v1/jobs/25
204

再以更密的调度周期(每 2 分钟)创建新作业,作业名 accuracy_job_exp3(请求体结构同 4.2 节,job.name 改为 accuracy_job_exp3、cron.expression 改为 0 0/2 * * * ?,保存为 /root/accuracy-job-fix.json 后执行):

curl -s -w "\nHTTP_CODE=%{http_code}\n" -X POST http://localhost:38080/api/v1/jobs \
  -H "Content-Type: application/json" \
  -d @/root/accuracy-job-fix.json

响应(输出节选,实测):

{"id":40,"quartz.name":"accuracy_job_exp3_1791990423152","quartz.group":"BA",...}
HTTP_CODE=201

6.3 predicate 通过与 Spark 执行核验

下一轮调度到达时,服务日志(/root/service/service.log)出现就绪检查通过的关键行(实测原文,... 为实测日志原样):

2026-10-14 15:20:15.765  INFO [ryBean_Worker-3] FileExistPredicator : Predicate path: hdfs://griffin:9000/griffin/data/batch/demo_src/dt=20261014/hour=14/_DONE
2026-10-14 15:20:15.793  INFO [ryBean_Worker-3] FileExistPredicator : Predicate path: .../_DONE exists.
2026-10-14 15:20:15.796  INFO [ryBean_Worker-3] FileExistPredicator : ... demo_tgt .../_DONE exists.

源表与目标表的 _DONE 均确认存在,Spark 作业随即经 Livy 提交上 yarn。等待约 1 分钟后查询 Livy 批作业:

curl http://localhost:38998/batches
{"id":0,"state":"success","appId":"application_1791990164994_0001","sparkUiUrl":"http://griffin:8088/proxy/application_1791990164994_0001/"}

(HTTP 200。state=success 表示 Spark 计算完成;sparkUiUrl 为服务器内部主机名视角的 yarn proxy 地址,宿主机可通过 38088 端口的 YARN 页面查看同一作业。)

再查作业执行实例:

curl "http://localhost:38080/api/v1/jobs/instances?jobId=40&page=0&size=10"

实例状态(输出节选,实测):

{"id":45,"sessionId":0,"state":"SUCCESS","appId":"application_1791990164994_0001",...}

state=SUCCESS 与 Livy 的 success、yarn 的 application_1791990164994_0001 三者对应,端到端链路(调度 → 就绪检查 → Spark on yarn 计算 → 指标写出)至此全部跑通。

七、结果核验:ES 指标与 metrics API

7.1 ES 指标索引

指标由度量定义中自动挂载的 ELASTICSEARCH sink 写入。实测索引列表:

curl http://localhost:39200/_cat/indices?v
health status index   uuid                   pri rep docs.count docs.deleted store.size
yellow open   griffin szaYHsDkTTGM6pXp2CiMIg   5   1          1            0      5.2kb

单节点环境下 1 副本无法分配,索引健康 yellow 属预期。检索准确率指标:

curl "http://localhost:39200/griffin/accuracy/_search?pretty&filter_path=hits.hits._source" \
   -d '{"query":{"match_all":{}},"sort":[{"tmst":{"order":"asc"}}],"size":3}'
{"hits":{"hits":[{"_source":{"name":"accuracy_job_exp3","tmst":1791987600000,
  "value":{"total":125000,"miss":502,"matched":124498}}}]}}

指标解读:total=125000 为比对总行数,miss=502 为不一致行数,matched=124498 为一致行数,准确率 = 124498/125000 ≈ 99.5984%——与 demo 数据预置的约 0.4% 缺失(502 行)完全吻合,说明指标计算正确。

7.2 metrics API 聚合查询

同样的指标经 Service API 按度量名聚合返回:

curl http://localhost:38080/api/v1/metrics
{"accuracy_measure":[{"name":"accuracy_job_exp3","type":"ACCURACY","owner":"test",
  "metricValues":[{"name":"accuracy_job_exp3","tmst":1791987600000,
  "value":{"total":125000,"miss":502,"matched":124498}}]}]}   HTTP 200

(HTTP 200,输出按"度量名 → 指标序列"组织,metricValues 数组随每轮执行追加,适合画趋势图。)

7.3 作业健康与后续调度

curl http://localhost:38080/api/v1/jobs/health
{"healthyJobCount":1,"jobCount":1}

(修复后 healthyJobCount 由 0 变 1。)作业 id=40 处于活跃调度状态:每 2 分钟到点后重复"就绪检查 → Spark on yarn 计算 → 指标追加写入 ES/HDFS",每次执行都会在 griffin/accuracy 索引与 /api/v1/metrics 的 metricValues 中追加一条新时间戳记录。验证方法:隔 2 分钟以上重查 7.1 节的 _search,hits.total 与 tmst 数量应增长;不再需要时按第八章方法删除作业即可停止调度。

八、常见问题(FAQ)

Q1:作业一直 NOT_FOUND,Livy 里也没有作业? 就是本篇第五章的完整链路。排查三步:一看 /root/service/service.log 的 Predicate path: ... doesn't exist. 行,拿到被探测的完整路径;二用元数据接口核对 Hive 表 location,确认真实数据目录;三把度量中 predicates 的 root.path 改成真实目录后 PUT 更新度量、重建作业。切勿只改 URI 写法(hdfs:/// 与 hdfs://host:9000 实测等价,0.2.0 上均可用),路径段缺失才是根因。

Q2:POST /api/v1/jobs/trigger/:id 返回 404? 实测 0.2.0 服务没有该端点(0.6.0 官方接口文档与 postman 集合里有)。解决:不依赖手动触发,等 cron 周期到点自动调度即可;如需手动验证,可把 cron.expression 临时调成 0 0/2 * * * ?(每 2 分钟)缩短等待,验证完再改回。

Q3:为什么分区小时与我的本地时间对不上? 本环境的时区分工实测为:作业调度时区 cron.time.zone=GMT+8:00,连接器数据时区 data.time.zone=UTC(WET,GMT),而 demo 数据的分区目录由服务器时钟(UTC)生成(实测 dt=20261014/hour=14/15/16)。定位分区时以 UTC 分区为准;若自建度量,建议把连接器时区与数据生成分区所用的时区统一,避免 #HH# 代入后找错分区。

Q4:ES 索引健康是 yellow,有影响吗? 无影响。默认指标索引 5 主分片 1 副本,单节点环境副本分片无法分配,yellow 属预期;检索与写入均正常(见 7.1 节实测)。多节点部署或调整副本数后即转 green。

Q5:PUT 更新度量后,里面的 id 变了? 正常行为。实测 PUT 后度量内 connector/predicate 子对象 ID 由 5/8 重建为 35/38,顶层 measure.id 不变。引用关系(measure.id)一律用顶层 ID,不要缓存子对象 ID。

Q6:指标会一直追加吗?磁盘怎么控制? 会。活跃作业每轮调度都追加一条指标(ES 索引 + HDFS 备份双写)。不需要时删除作业停止调度:DELETE /api/v1/jobs/{id} 返回 204 即停;ES 侧历史指标可按 tmst 时间戳清理或按留存策略滚动索引。

九、参考链接