继续排查问题
写在前面
默认使用ceph的对象存储功能,在rados层使用的是数据与元数据划分不同存储池进行分别存放的方案,具体来说就是对象的数据部分存放在data pool,而元数据部分,如对象索引、用户信息、gc及lc信息等,是存放在其他存储池,这样做的好处是可以根据不同的数据特点,将存储池划分到不同的磁盘介质上,充分发挥硬件的优势,同时能兼顾比较好的性价比,具体的分析和集群建设问题松鼠哥已经在课程中进行了详细的介绍。
数据部分的特点是数据体积较大,放在hdd上比较划算,而元数据的特点是数据体积小但数量往往很大,因此放在ssd上可以加速对象存储的性能,然而本次遇到的问题是元数据存储池的osd用量莫名地大,所以有了本篇的调查。
本篇的重点在解决思路,授人与鱼不如授人以渔,思路才是最重要的。
开始
首先是现象,没什么特别,就是告警提示有osd挂掉了,且明显不是坏盘引发。
接下来是确定挂掉的原因,直接去看日志,发现是这样的:
1 | -10001> 2024-10-09 05:36:22.267 7f110346d700 1 bluefs _allocate failed to allocate 0x41e9aee on bdev 1, free 0x1120000; fallback to bdev 2 |
看ceph的日志排查问题,最重要的是看-1
的日志,很多同学截图日志往往get不到重点,其实重点就是-1
的日志,别的多数情况下都不太重要,这里很清楚了,-1
的日志显示osd尝试从2个dev上分配空间都失败了,所以assert了。
定位到直接原因后,就要展开排查思路了,既然是osd空间分配问题,那么看下它的空间情况
1 | [root@test-mon1 ~]# ceph osd df|grep 1628 |
不管是集群层面还是监控,都没有显示osd达到full也就是理论上还有可用空间的,显然目前osd只用了83%,那么为什么rocksdb无法申请到空间了呢?
一个bluestore做的osd,其磁盘空间一般来说我们可以分成2部分:
- 一部分是osd的data区域,这在osd中用来存放数据部分,具体是osd目录中的block指向的块设备。
- 另外一部分是db区域,具体是osd目录中的block.wal和block.db,其中block.wal实质为rocksdb的一部分。
当创建osd时我们可以单独指定某些块设备或者卷作为block.db和block.wal,也可以不单独指定,当我们仅指定data时,osd创建以后,db和wal就自动在data上分配空间,默认为block.db分配data空间的4%作为自己的可用空间,当rocksdb自己的可用空间用完后,就会往data上分,如果data也没有空间可以分出,就会报上述的错误。
这个osd.1628就是没有单独分出rocksdb和wal的单个ssd磁盘建成的osd,用来存放几个对象存储元数据池的数据:
1 | [root@test-mon1 ~]# ceph df |
从元数据存储池的用量统计可以看到,只有non-ec
池的用量较大,其他池几乎没有占用。
存储池存储数据时,实际上会有两种形式,一种是数据直接写入的形式,另外一种是omap的形式。数据直接写入的形式,就是将op过来的数据直接写到osd的data区,而omap的形式,则是将op过来的数据以kv的方式写入到rocksdb中,由于集群存放的数据量庞大,index存储池的占用应该是很大的,但是在ceph df
中没有体现,是因为index pool的数据都是以omap的形式存放,这部分空间无法在ceph df
中看到,只能通过ceph osd df
观察具体的osd用量才能看到:
1 | [root@test-mon1 ~]# ceph osd df|grep -E "1628|OMAP" |
接下来,排查的思路是调查data和db的空间使用情况。
首先是rocksdb的空间情况,定位到离assert最近的一个lsm_state
,它是db空间使用的直接统计,看下它的实际空间情况:
1 | -10001> 2024-10-09 05:36:15.495 7f1103c6e700 4 rocksdb: (Original Log Time 2024/10/09-05:36:15.496359) EVENT_LOG_v1 {"time_micros": 1728423375496349, "job": 167, "event": "compaction_finished", "compaction_time_micros": 39492399, "output_level": 3, "num_output_files": 25, "total_output_size": 1680590352, "num_input_records": 4104963, "num_output_records": 3783129, "num_subcompactions": 1, "output_compression": "NoCompression", "num_single_delete_mismatches": 0, "num_single_delete_fallthrough": 0, "lsm_state": [3, 49, 421, 3075, 0, 0, 0]} |
大致算了一下,rocksdb使用的总空间大小为(3+49+421+3075)*64MiB
,跟ceph osd df
中显示的osd的omap用量差不多,那么data部分呢?
前面说过,data部分是用来存放数据写入的,但是这些osd承载的都是对象存储的元数据池,理论上应该不会产生data写入,而只有omap写入才对,唯一有可能产生data写入的是non-ec
的存储池,而且显然它的对象数量很大。
前述命令显示,non-ec
的用量只有1.1GiB
,怎么会造成这么大的data写入呢?
non-ec
这个存储池的作用,是在对象存储写入中,当使用multipart方式上传时,rgw需要往这个pool中写入一条记录,当多个块都上传完成时,rgw需要根据记录来完成这些块的合并,合并完成后就会删除这个记录,另外,分块上传失败时,用户手动进行abort,通知rgw上传终止,也会删除这个记录,如果合并失败且没有abort,这个记录就不会被删除,这就存在残留的风险。
哪怕是记录残留,4千多万的对象只有1.1GiB
,会造成osd的data写满吗?
别说不会,看一下这些对象什么情况,随机挑选一个看看:
1 | [root@test-mon1 ~]# rados -p songshuge.rgw.buckets.non-ec stat b041a41c-3253-4f05-aef0-bc753b1c061b.b7e53454.766__multipart_12420000441435122B/test1.mp4.tmp.2~ABUw1IQCFRBtnPiAIEgPOjJr62SPqWR.meta |
最后一个字段显示,每个记录是27B
,好家伙,这么小!
一个记录27B
,4千多万个记录确实才1GiB,这有什么问题?
当然有问题,这里就比较深入了,需要知道,当一个data写提交到osd时,由于每个写入都是单独的对象(可以把osd看作对象存储),osd需要为这个对象申请空间来存放,出于效率考虑,这个申请的空间大小有个最小值,ssd默认是16k(bluestore_min_alloc_size_ssd),那么,为了存下这个27B的数据,osd需要在data区域申请一个最小的16k的块进行写入,这里就产生了606倍的空间放大。
这就离谱!为什么这么小体积的数据要写data而不用omap方式写?why??是因为使用omap存的话难度很大吗?
解决
明确原因和基本逻辑后,解决思路就基本有几个:
- 写个外挂工具巡检这个
non-ec
池,删掉过期的non-ec
残留的meta记录 - 压力给到用户,让用户去abort这些失败的操作,rgw会去清理这些数据
- 为bucket设置lc,定期清理残留的碎片和记录(S3支持)
- 格局,不就是空间放大嘛,让隔壁老外看到以为咱们加不起呢,咱加盘,加最贵的,狠狠地加,每台加满
这些思路都是阔以的,同学们可以按需执行~
总结
由于集群没有警告osd的full,这种空间放大问题比较隐蔽,不容易排查,总体思路是比较明确的:
- 1、确定直接原因,去看日志呗
- 2、捋顺逻辑,查询各方数据,确定大体情况
- 3、osd空间无非就是data和db,分类调查下去,总会发现蛛丝马迹
- 4、大胆假设,小心论证,最终得到确切结论和解决方案
都看到最后了,觉得有收获的老铁给松鼠哥赏一个吧,谢谢~
- 本文作者: 奋斗的松鼠
- 本文链接: http://www.strugglesquirrel.com/2024/11/11/一次对象存储元数据osd空间放大问题调查/
- 版权声明: 本博客所有文章除特别声明外,创作版权均为作者个人所有,未经允许禁止转载!