SkyWalking 升级 7.x 后 Hour/Day 精度指标索引停止更新:原因分析与清理方案
【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking
本文聚焦 Apache SkyWalking OAP 从 6.x 升级到 7.x 后,Elasticsearch 存储中
*-hour_xxxxx与*-day_xxxxx索引不再更新的常见问题。文章从官方 FAQ 出发,结合 7.0.0 变更记录、Elasticsearch 存储插件源码与索引命名实现,解释该现象是“Downsampling Data Packing(降采样数据打包)”特性生效后的预期行为,并给出删除过期索引、核对当前索引模型、调整dayStep与 TTL 的完整实战方案。读完本文,你将能够判断升级后的索引差异是否正常,并安全地完成历史索引清理与存储配置调优。
问题现象:升级后 Hour/Day 索引停止写入
在将 SkyWalking OAP 从 6.x 升级到 7.x 后,部分用户发现 Elasticsearch 中带有hour与day时间精度的指标索引不再有新数据写入,表现为:
service_instance_xxx-day_20260919这类日精度索引的文档数长时间不再增长;service_xxx-hour_20260919这类小时精度索引同样停止更新;- 只有形如
service_instance_xxx-20260919(分钟精度,无精度关键词)与service_instance_xxx-month_202609的索引仍在持续写入。
这并非故障,而是 7.x 存储实现变化导致的预期行为。官方在 Hour-Day-Metrics-Stopping.md 中明确说明:该问题源于 Elasticsearch 存储的Downsampling Data Packing(降采样数据打包)特性。
建议先阅读 Elasticsearch 存储配置文档 了解索引相关配置,再结合本文处理索引变更。
根因:7.x 将 Hour/Day 降采样数据打包进分钟索引
7.0.0 变更记录中的关键一行
在 changes-7.0.0.md 的 OAP-Backend 章节,有一条与本问题直接相关的变更:
Merge the HOUR and DAY metrics into MINUTE in the ElasticSearch storage implementation. Reduce the payload for ElasticSearch server.
即:从 7.0.0 开始,SkyWalking OAP 在 Elasticsearch 存储实现中,不再为 HOUR 与 DAY 精度的指标单独创建独立索引,而是将它们统一合并写入 MINUTE(分钟)精度的索引中。官方 FAQ 也印证了这一点:
Currently, SkyWalking uses the
metrics name-xxxxxandmetrics name-month_xxxxxindexes only.
其中metrics name-xxxxx对应分钟精度索引(xxxxx为日期时间戳),metrics name-month_xxxxx对应月份精度索引。也就是说,升级后实际使用的只有这两类索引。
降低存储开销的设计意图
该变更是为了减少 Elasticsearch 集群的存储与 IO 压力:此前 HOUR/DAY 降采样数据各自维护独立索引,会产生大量冗余副本;合并进分钟索引后,同一份数据即可支撑秒/分钟/小时/日多个查询精度,不再需要为高层级精度重复落盘。
这一设计也与 7.0.0 同期引入的Daily Index Step(每日索引步长)配合(见 changes-7.0.0.md 中 "Support Daily step in the ElasticSearch storage implementation for low traffic system" 一行),共同构成 7.x 以降采样打包 + 按日滚动索引为核心的存储策略。
索引命名模型:从源码看 7.x 之后如何组织索引
要确认“当前到底使用哪些索引”,可以从 Elasticsearch 存储插件的索引名生成逻辑入手。核心类为 TimeSeriesUtils.java,其中writeIndexName(Model model, long timeBucket)根据模型的降采样级别(DownSampling)决定索引后缀:
static String writeIndexName(Model model, long timeBucket) { String tableName = IndexController.INSTANCE.getTableName(model); if (model.isRecord() && model.isSuperDataset()) { // 记录型超大数据集(如 trace segment)走 superDatasetDayStep return tableName + Const.LINE + compressTimeBucket(timeBucket / 1000000, SUPER_DATASET_DAY_STEP); } else { switch (model.getDownsampling()) { case None: return tableName; // 非时序数据,无时间后缀 case Hour: return tableName + Const.LINE + compressTimeBucket(timeBucket / 100, DAY_STEP); case Minute: return tableName + Const.LINE + compressTimeBucket(timeBucket / 10000, DAY_STEP); case Day: return tableName + Const.LINE + compressTimeBucket(timeBucket, DAY_STEP); case Second: return tableName + Const.LINE + compressTimeBucket(timeBucket / 1000000, DAY_STEP); default: throw new UnexpectedException("Unexpected down sampling value, " + model.getDownsampling()); } } }从源码结构可以推断出索引命名的换算逻辑:
- 分钟(Minute)精度:时间桶除以
10000后拼入索引名,得到yyyyMMdd日期后缀,即metrics-20260919; - 小时(Hour)精度:时间桶除以
100后拼入索引名,同样是yyyyMMdd日期后缀; - 日(Day)精度:时间桶直接拼入索引名,仍是
yyyyMMdd日期后缀; - 月(Month)精度:对应
metrics-month_202609形式(month_前缀 +yyyyMM年月后缀)。
关键点在于:Hour、Minute、Day 三种精度最终都会落到同一个yyyyMMdd后缀的索引里。因此在 7.x 中,service-20260919这一个索引同时承载了分钟、小时、日三个降采样级别的数据,这正是“Hour/Day 索引停止更新”的直接原因——它们不再有独立索引了。
降采样级别的枚举定义见 DownSampling.java:
public enum DownSampling { None(0, ""), // 非时序数据 Second(1, "second"), // 用于 record、profile、top n 等明细数据 Minute(2, "minute"), Hour(3, "hour"), Day(4, "day"); }TTL 清理逻辑:为什么旧索引不会再被写入
7.x 的 TTL(数据保留期)删除逻辑也印证了这一打包策略。见 HistoryDeleteEsDAO.java 的deleteHistory方法:
if (!model.isRecord()) { if (!DownSampling.Minute.equals(model.getDownsampling())) { /* * In ElasticSearch storage, the TTL triggers the index deletion directly. * As all metrics data in different down sampling rule of one day are in the same index, the deletion operation * is only required to run once. */ return; // 非分钟精度的指标模型,跳过独立 TTL 删除 } }源码注释明确说明:“一天内不同降采样规则的所有指标数据都在同一个索引中,删除操作只需执行一次”。因此:
- TTL 任务只针对分钟精度模型执行删除,因为分钟索引已包含 Hour/Day 数据;
- 删除时按索引名中的日期时间戳与
deadline = now - ttl比较,过期即删(见 HistoryDeleteEsDAO.java 中isolateTimeFromIndexName与deleteByIndexName的调用); indexLatestSuccess缓存避免了同一批索引被重复扫描删除。
所以,那些遗留的*-day_xxxxx、*-hour_xxxxx索引不会被 TTL 任务管理(它们属于 6.x 时代创建的旧索引),需要手动处理。
处理方案:安全删除过期的 Hour/Day 索引
官方 FAQ 给出的建议非常直接:
You may simply delete all expired
*-day_xxxxxand*-hour_xxxxx(xxxxxis a timestamp) indexes.
即:删除所有已经过期的*-day_xxxxx与*-hour_xxxxx索引(xxxxx为时间戳,如20260919)。操作步骤与注意事项如下。
1. 确认当前生效的索引模型
删除前,先通过 Elasticsearch 的_cat/indices接口列出所有索引,确认升级后仍在写入的索引集合:
# 查看所有 SkyWalking 相关索引(可按 namespace 前缀过滤,默认 sw_) curl -s 'http://localhost:9200/_cat/indices/sw_*?v&s=index'正常情况下,7.x 集群中指标索引应呈现为:
sw_service-20260919(分钟精度,同时承载 Hour/Day 数据);sw_service-month_202609(月精度);- 记录型索引如
sw_segment-20260919、sw_log-20260919等。
而那些sw_service-day_20260919、sw_service-hour_20260919形态的索引,即为 6.x 遗留、升级后不再写入的旧索引。
2. 按时间筛选并删除过期索引
结合_cat/indices的creation.date或索引名中的时间戳,筛选出超过 TTL 保留期的旧索引后删除:
# 例:删除命名符合 *-day_* 的索引(请先替换为实际的保留期过滤条件) curl -s -X DELETE 'http://localhost:9200/sw_service-day_20260819,sw_service-hour_20260819'也可以借助索引别名批量管理。7.x 的 TTL 删除通过retrievalIndexByAliases(tableName)拿到索引集合后逐个删除(见 HistoryDeleteEsDAO.java),手动清理时同样建议先确认索引名再执行删除。
务必先备份或确认索引数据已无保留价值。如果这些旧索引中还有需要继续查询的历史数据,应先通过 reindex 迁移到新索引,或延长保留期后再清理,避免误删。
3. 关于xxxxx时间戳的说明
FAQ 中的xxxxx即索引名中的时间戳,例如:
sw_service_instance-day_20260919中的20260919表示该索引覆盖 2026-09-19 当天的日精度数据;sw_service-hour_20260919中的20260919表示该索引覆盖 2026-09-19 当天的小时精度数据。
清理时只需依据这个时间戳判断是否已超出 TTL 即可。
进阶:dayStep 与 TTL 的联动配置
理解 7.x 的索引打包机制后,还需注意两个会影响索引行为与清理节奏的配置项,它们定义在 elasticsearch.md 中:
storage: elasticsearch: # ...... dayStep: ${SW_STORAGE_DAY_STEP:1} # Represent the number of days in the one minute/hour/day index. superDatasetDayStep: ${SW_STORAGE_ES_SUPER_DATASET_DAY_STEP:-1} # Represent the number of days in the super size dataset record index, the default value is the same as dayStep when the value is less than 0dayStep:一个索引容纳几天的数据
dayStep(默认 1)表示一个分钟/小时/日索引覆盖的天数。文档给出的例子:当dayStep == 11时:
[2000-01-01, 2000-01-11]的数据被合并进索引index-20000101;[2000-01-12, 2000-01-22]的数据被合并进索引index-20000112。
对应到源码,就是 TimeSeriesUtils.java 中compressTimeBucket的取模归并逻辑:
static long compressTimeBucket(long timeBucket, int dayStep) { if (dayStep > 1) { DateTime time = TIME_BUCKET_FORMATTER.parseDateTime("" + timeBucket); int days = Days.daysBetween(DAY_ONE, time).getDays(); int groupBucketOffset = days % dayStep; return Long.parseLong(time.minusDays(groupBucketOffset).toString(TIME_BUCKET_FORMATTER)); } else { return timeBucket; // dayStep=1 时无需计算 } }DAY_ONE被固定为20000101(见 TimeSeriesUtils.java 中的DAY_ONE常量),确保无论 OAP 何时启动,基于dayStep的索引分组始终一致。
dayStep的典型适用场景:低流量环境下希望设置很长的 TTL(如超过 60 天),但 ES 集群承载能力有限。此时可将dayStep调大(如 5 或更多),让单个索引容纳多天数据,减少索引数量。配置生效路径在 StorageModuleElasticsearchProvider.java:
if (config.getDayStep() > 1) { TimeSeriesUtils.setDAY_STEP(config.getDayStep()); TimeSeriesUtils.setSUPER_DATASET_DAY_STEP(config.getDayStep()); } if (config.getSuperDatasetDayStep() > 0) { TimeSeriesUtils.setSUPER_DATASET_DAY_STEP(config.getSuperDatasetDayStep()); }调整 dayStep 时必须同步放大 TTL
官方文档特别提醒:
NOTE: TTL deletion would be affected by these steps. You should set an extra dayStep in your TTL. For example, if you want to have TTL == 30 days and dayStep == 10, you are recommended to set TTL = 40.
即 TTL 删除受dayStep影响,设置 TTL 时需要额外加上一个dayStep的余量。例如期望保留 30 天数据、dayStep == 10时,建议将 TTL 设为 40。原因在于:索引是按dayStep天分组的,删除以整个索引为单位,最后一个未满的索引组也会被整体保留(或整体删除),多出一个dayStep的缓冲可以避免边界数据被提前清理。
总结与自查清单
升级 7.x 后*-hour_xxxxx、*-day_xxxxx索引停止更新,属于 7.0.0 “将 HOUR/DAY 指标合并进 MINUTE 索引”的预期行为。落地时可按下述清单自查:
| 检查项 | 预期结果 | 依据 |
|---|---|---|
| 当前使用的指标索引 | 仅metrics-xxxxx(分钟)与metrics-month_xxxxx(月) | Hour-Day-Metrics-Stopping.md、changes-7.0.0.md |
6.x 遗留的*-hour_*/*-day_*索引 | 不再写入、不再被 TTL 管理,需手动删除 | HistoryDeleteEsDAO.java |
| 分钟/小时/日数据是否仍可查询 | 是,三者共用同一yyyyMMdd后缀索引 | TimeSeriesUtils.java |
TTL 与dayStep | TTL 应额外加上dayStep余量 | elasticsearch.md 中 Daily Index Step 小节 |
一句话结论:升级 7.x 后出现 Hour/Day 索引停止更新是设计使然——只需手动删除过期的*-hour_xxxxx与*-day_xxxxx旧索引;若同时调整了dayStep,务必同步放大 TTL,并确认分钟索引已完整承载各精度数据后再清理。
【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考