记录一次Influxdb频繁OOM问题排查过程。
问题现象
业务方发现写入Influxdb超时,实例http://influxdb:8086/ 接口调用超时。
问题查看
登陆influxdb查看进程状态,发现influxdb实例存活,查看influxdb实例启动时间为业务超时时间
前后。通过简单查看influxdb服务发现该服务使用systemd进行服务管理,并且配置了on-failer=restart
策略,导致进程挂掉后会自动重启实例。
demsg
查看后,发现触发了系统的OOMKiller,导致Influxdb进程被kill掉了。
[4312996.555762] Out of memory: Kill process 30539 (influxd) score 979 or sacrifice child
[4312996.557382] Killed process 30539 (influxd) total-vm:86617144kB, anon-rss:64314244kB, file-rss:0kB, shmem-rss:0kB
[4314332.278302] java invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
思考: 其实任何服务配置了自动重启策略在大多数场景都会比较有隐患,因为如果业务侧的敏感度较低,该策略会在异常时自动重启,对于业务影响就是重启的节点服务不稳定,但是从长远角度来讲,其实是埋下了一个祸根,因为重启永远不能解决根本问题。而且随着时间的推移,风险会一直增长。
问题排查
1. 添加InfluxDB进程监控
由于influxdb缺少一些基础进程和指标监控,导致每次重启后都是由业务方反馈出来的,因此刚开始很难知道那个时间点进行了重启,并且无法很直观去对比重启时间点前后的任务并进行分析。所以排查的第一步就是先加个进程监控,去及时发现进程在什么时间点OOM了,然后对照基础监控,对比前后时间点的异常问题进行分析.
$ crontab -l
*/10 * * * * /bin/bash /root/bgbiao/check_influxdb_start_time.sh influxdb
$ cat /root/xxb/check_influxdb_start_time.sh
#!/bin/bash
process=$1
nowday=`date +%Y%m%d`
pid=$(ps -ef | grep $process | grep "/usr/bin/" | grep -v grep | awk '{print $2}')
echo $pid
starttime=`ps -eo pid,lstart,etime | grep ${pid}`
echo ${starttime} >> /root/bgbiao/${nowday}.log
$ cat 20190722.log | awk '{print $1 " "$5}' | sort -u -k 2
16527 11:19:36
26477 14:18:55
843 16:21:22
7691 18:17:01
10415 19:03:11
11462 19:27:25
13892 20:09:09
14439 20:17:41
29097 21:17:27
27207 23:18:52
$ cat 20190723.log | awk '{print $1 " "$5}' | sort -u -k 2
30586 00:17:46
1582 01:17:37
5106 02:17:22
8585 03:20:50
11075 04:03:09
11896 04:17:33
18683 06:17:07
25491 08:17:10
28991 09:21:21
6090 10:22:29
10870 11:17:49
25714 12:17:44
28253 13:00:31
24379 14:17:26
27917 15:19:33
31417 16:17:46
2645 17:17:35
6256 18:19:25
9590 19:17:37
13166 20:17:40
15018 20:48:46
16799 21:17:53
23597 23:17:29
27207 23:18:52
$ cat 20190724.log | awk '{print $1 " "$5}' | sort -u -k 2
27057 00:18:34
30548 01:25:02
1173 02:18:29
4654 03:17:43
8358 04:17:57
11801 05:17:28
15244 06:17:37
18690 07:17:32
22158 08:18:06
25575 09:17:43
29220 10:17:52
32748 11:17:43
4199 12:18:55
7613 13:17:42
11090 14:17:31
15237 15:17:34
18792 16:17:44
22363 17:17:28
25808 18:17:48
29287 19:17:38
314 20:17:44
3926 21:17:34
7450 22:18:01
23597 23:17:29
10914 23:17:36
从前两天的监控来看,发现该influxdb的OOM问题非常严重,平均一小时OOM一次,然后实例自动重启,由于Influxdb在重启过程中需要加载磁盘中的数据,过程中会有短暂的几分钟业务不可用,此时业务方就会立马反馈出来,但是由于实例很快就重启,非敏感型的业务通常也关注不到,或者不关注该问题。
发现OOM和重启时间点后就和主机的基础监控进行对比,其实发现只有内存在OOM前会大量的增加(基本上突然达到90%后会一直上涨,而后就是OOM,继而systemd重启实例,奇怪的是阿里云也有Influxdb的进程级别的内存监控,进程的内存却一直很平稳)
2. InfluxDB问题追踪
既然发现了OOM的时间点,也对比了监控,除了内存,其他也没什么性能瓶颈,那就需要稍微深入Influxdb进行问题追踪了。
由于之前最早在搞Docker的时候用过TIG
(Telegraf+Influxdb+Grafana)方案,对Influxdb有一定了解,知道如果在并发查询很大的情况下磁盘会是瓶颈(毕竟是在磁盘上直接读写数据),并且单库的序列(series)不能太多,但是当时由于主要监控基础资源的监控指标,数据量和组合序列也并不是很多,并没有太细致的关注。
深入Influxdb官网发现如下描述:
InfluxDB maintains an in-memory index of every series in the system. As the number of unique series grows, so does the RAM usage. High series cardinality can lead to the operating system killing the InfluxDB process with an out of memory (OOM) exception. See SHOW CARDINALITY to learn about the InfluxSQL commands for series cardinality.
大致意思就是说,InfluxDB会在系统上为每个series
维护一个内存索引
,而随着这些series的增加,RAM内存使用率也会增加。如果series cardinality如果太高,就会导致操作系统触发OOMKiller机制,将Influxdb进程KILL掉. 使用SHOW CARDINALITY命令可以查看到series cardinality
。
注意: 一般情况下,一定要查阅当前版本匹配的文档
好了,看到如上官方说明,先去看下influxdb上各个库的series统计:
# 查看目标库的series cardinality
$ influx -execute "SHOW SERIES CARDINALITY on kafka_monitor"
cardinality estimation
----------------------
833
$ influx -execute "SHOW SERIES CARDINALITY on recommend"
cardinality estimation
----------------------
5853490
$ influx -execute "SHOW SERIES CARDINALITY on recommend_day"
cardinality estimation
----------------------
15848
$ influx -execute "SHOW SERIES CARDINALITY on rtbusimon"
cardinality estimation
----------------------
5000128
$ influx -execute "SHOW SERIES CARDINALITY on rtmysql"
cardinality estimation
----------------------
881925
$ influx -execute "SHOW SERIES CARDINALITY on datamon"
cardinality estimation
----------------------
114
从上面的基础排查中,可以发现rtbusimon
和recommend
两个库的series cardinality太多,也就是很可能是它俩造成的OOM,和业务方沟通过后发现rtbusimon
库的数据已经不再使用,因此基本可以断定是recommend
库导致的。
基本发现问题之后,很快就有验证猜测和解决方案。要么让业务库优化schema,要么优化influxdb(限制内存或者优化配置),很明显优化schema是一个长期的问题,而且该influxdb上运行了其他业务库,为了验证是recommend
造成的OOM,并且为了隔离影响,决定将recommend
库迁移出去,另外使用内存限制和其他influxdb优化策略来尝试解决。
在迁移recommend
之前,其实也做了很多配置优化,比如前面官方说到了,系统会为每个series
维护一个内存索引,也就是说这些series太多,数据量较大,且又在内存里,因而导致内存使用率不断上升
,因此其实可以在这个索引上去寻找优化方案。
在InfluxDB官方配置文档中发现一个索引参数index_version
,用来指定索引类型,默认是inmem
,也就是将上面这些series信息的索引维护在内存里,同事还支持tsi1
类型,该中类型是后来版本中支持的一种索引类型,主要优化是将索引数据存储在磁盘上,并使用冷热数据分离的方式让整个库可以支持更多的series。(幸亏我们使用的是Influxdb1.X的最高版本1.7,是支持tsi1索引类型的)
influxdb-tsi索引细节
在influxdb老库上修改index-version = "tsi1"
,等待下次OOM后重启实例生效,但是观察了几个小时后发现依然会不断OOM,好像tsi1
索引类型不生效,查看相关日志看到如下日志:
Jul 24 04:18:17 izbp169jm0y04a93txacaqz influxd: ts=2019-07-23T20:18:17.278376Z lvl=info msg="Opened shard" log_id=0Gp7_f5l000 service=store trace_id=0Gp7_fVW000 op_name=tsdb_open index_version=inmem path=/var/lib/influxdb/data/kafka_monitor/kafka_mointor_rp/1187 duration=97.488ms
Jul 24 04:18:17 izbp169jm0y04a93txacaqz influxd: ts=2019-07-23T20:18:17.280005Z lvl=info msg="Opened shard" log_id=0Gp7_f5l000 service=store trace_id=0Gp7_fVW000 op_name=tsdb_open index_version=inmem path=/var/lib/influxdb/data/kafka_monitor/kafka_mointor_rp/1192 duration=187.117ms
果真,虽然已经改为index_version="tsi1"
了,但是貌似数据依然使用的是inmem
类型,导致series数据依然使用内存索引,influxdb依然是每小时OOM重启。但是,不应该啊,毕竟官网说了tsi1
是磁盘索引,可是为啥不生效呢
继续排查相关资料,发现官网的一片博客Path to 1 Billion Time Series: InfluxDB High Cardinality Indexing Ready for Testing,基本上也是他们对series-cardinality较高场景的一些测试,其中也提到了使用tsi1
索引来满足这种高基序列(high-series-cardinality)的处理,同时需要注意的是,修改完tsi1
索引后首先要重启才能生效,并且如果influxdb已经有一些数据,那么老分片上的数据将会一直使用inmem
索引类型,而只有新分片上的数据才会使用tsi1
的磁盘索引方式。
那么如何区分哪些分片是使用inmem
还是tsi1
索引类型呢,可以查看分片目录,如果有index
目录,即表示该分片使用tsi1
的磁盘索引。
# 后面那个数字其实就是分片id,通常id都是递增的,因此一般比较大的分片id应该就是新创建的分片
$ ls /var/lib/influxdb/data/recommend/one_month_only/1023
000000010-000000002.tsm fields.idx
$ ls /var/lib/influxdb/data/recommend/one_month_only/1504
000000017-000000002.tsm fields.idx index
基本上确认tsi1
索引不生效就是因为仍然有很多数据落在老的分片上。确定了该问题后,为快速验证是recommend
导致的OOM,且避免影响其他业务库,当即作出迁移recommend
到其他主机上。
迁移完成后,老库监控的Influxdb(2019-07-25 17:30 正式将recommend库切换到新库运行)进程启动时间如下:
[root@izbp169jm0y04a93txacaqz xxb]# cat 20190725.log | awk '{print $1 " "$5}' | sort -u -k 2
14373 00:17:35
17838 01:17:36
21287 02:17:43
24738 03:18:56
28149 04:17:31
31654 05:17:28
2661 06:17:28
6239 07:17:25
9714 08:17:49
13160 09:17:31
16624 10:17:46
20105 11:17:43
23567 12:17:40
27035 13:17:42
30539 14:17:50
306 14:55:21
1634 15:17:37
5204 16:17:26
8695 17:17:29
10914 23:17:36
[root@izbp169jm0y04a93txacaqz xxb]# cat 20190726.log | awk '{print $1 " "$5}' | sort -u -k 2
8695 17:17:29
[root@izbp169jm0y04a93txacaqz xxb]# cat 20190727.log | awk '{print $1 " "$5}' | sort -u -k 2
8695 17:17:29
可以看到,将recommend
库从老库迁移出去后,老库在2019-07-25
号最后一次启动进程信息是: 8695 17:17:29
,且在后续的两天20190726和20190727
两天内,该influxdb实例没有再出现过OOM,进程重启问题没有再出现.
好了,到现在为止,已经确认了series cardinility
太高的确会导致OOM,且inmem
的方式会随着series
的增加,不断占用系统内存。优化的方式就是尽量不要设计太多series
,同时推荐使用tsi
索引类型+SSD方式来权衡.
- 解决方案
验证到了问题,并且找到修复问题的方法,接下来就是如何不影响业务的平滑迁移。
其实刚开始迁移的时候,和业务方的沟通的结果是不迁移数据,只是在新库创建库以及库的数据保留策略和聚合策略,由业务方进行双写,也就是既写老库,也写新库,写新库的目的是为了重新生成分片,使tsi1
的索引类型生效,等数据量满足业务需求时,再进行平滑的库切换。但由于老Influxdb实例有其他业务的库也在用,为了尽快剥离recommend
库对其他业务的影响,我们将recommend
库数据也一并迁移过去了,不过迁移完成之后,噩耗才刚刚开始。因为数据迁移是使用influxd backup和restore
完成的,此时会将数据的分片信息也一并迁移过去,结果是就是虽然新库有了老数据,但是老数据的分片信息也是旧的,依然使用的是inmem
的索引类型。结果就是,该新库依然频繁OOM。
进程监控信息如下:
$ cat 20190725.log | awk '{print $1 " "$5}' | sort -u -k 2
20465 08:04:59
18065 16:33:01
22063 18:16:20
22311 19:17:27
22556 20:19:15
22749 21:17:44
22965 22:17:23
23120 23:01:50
23179 23:19:56
$ cat 20190726.log | awk '{print $1 " "$5}' | sort -u -k 2
23331 00:01:58
23379 00:16:33
23526 01:01:07
23584 01:19:33
23734 02:02:35
23792 02:16:29
23946 03:05:05
23995 03:16:39
24186 04:19:11
24448 06:01:16
24499 06:16:44
24683 07:16:41
24884 08:18:35
25071 09:19:21
25272 10:19:23
25472 11:19:16
25547 11:38:38
25719 12:16:24
25889 13:19:45
25961 13:38:37
26099 14:16:20
26438 16:16:49
26613 17:16:32
26796 18:16:28
26970 19:16:25
27163 20:20:08
27382 21:14:53
27536 22:01:13
27592 22:19:28
23179 23:19:56
27764 23:28:06
$ cat 20190727.log | awk '{print $1 " "$5}' | sort -u -k 2
27912 00:02:16
27963 00:16:54
28129 01:03:31
28188 01:18:26
28350 02:15:26
28507 03:03:00
28568 03:21:35
28737 04:09:33
28783 04:16:31
28925 05:02:26
28975 05:16:30
29117 06:01:08
29169 06:19:12
29317 07:02:00
29400 07:20:01
29521 08:01:04
29584 08:16:30
29760 09:17:24
29951 10:19:26
30134 11:20:26
30305 12:20:11
30534 13:58:39
30643 14:20:08
30787 15:19:10
27764 23:28:06
好吧,依然是平均一个多小时重启一次。查看索引类型如下:
$ cat /var/log/messages | grep -oP 'index_version=(inmem|tsi1)' | sort | uniq -c
6631 index_version=inmem
512 index_version=tsi1
发现tsi1
索引类型的数据几乎只占了全部数据的不到10%,剩余的数据依然使用inmem
的方式来存储每个series
信息。和业务方沟通后的另外一个方案就是,重新创建一个库,由业务方使用接口重新对历史数据进行补充写入(相当于是使用tsi索引类型重新往一个新库进行全新数据写入)。接下来就要靠业务方将全部数据写入新的tsi
索引类型的库后继续观察了。
后续有进展的话,再继续补充吧。
欢迎关注我的公众号