홈랩 Ceph 클러스터가 매시 15분만 되면 30초에서 1분 동안 클라이언트 요청을 처리하지 못했습니다. CephFS 메타데이터 풀도, VM 디스크(RBD) 풀도 같이 막혔고요. 처음 눈치챈 건 Nextcloud가 그 시각마다 “not ready”를 띄워서였지만, Nextcloud는 피해자였을 뿐 진짜 문제는 그 아래 Ceph였습니다. 같은 시각에 VM들 iowait도 같이 튀었으니까요.
시계처럼 정확한 장애는 보통 누군가 cron으로 부르고 있다는 뜻입니다. ㅎㅎ 결론부터 말하면 범인은 CephFS 스냅샷 정리가 일으킨 snaptrim 폭주였고, 인터넷에서 흔히 보는 처방(osd_snap_trim_sleep)은 mClock 스케줄러에서는 아예 통하지 않았습니다. 그래서 통하는 손잡이를 찾아야 했습니다.
🧭 환경
- Proxmox 노드 5대 위의 Ceph Squid 19.2.5, OSD 24~26개, 스케줄러는 기본값인 mClock
- Nextcloud 사용자 데이터는 CephFS(
cephfs-userdata-nvme, 데이터 풀 64 PG), VM 디스크는 RBD 풀 - 그 트리를 매시 :15에 외부 cron이 스냅샷합니다. (
fs snap-schedule이 아니라 직접 짠 스크립트) - 오래된 스냅샷은 같은 스크립트가 보존 기간에 맞춰 지웁니다
🔎 증거: ceph.log에 :15마다 찍히는 것들
/var/log/ceph/ceph.log를 :15 전후로 잘라 보면 매번 똑같은 게 찍혀 있었습니다.
SLOW_OPS ... slow requests
MDS_SLOW_METADATA_IO
slow requests on cephfs metadata pool, osdpool_rbd_diskimage_nvmeCephFS 메타데이터 풀뿐 아니라 VM 디스크 RBD 풀에서도 요청이 막혔습니다. VM iowait가 같이 튄 이유가 이겁니다. 순서는 이렇습니다.
- :15에 새 스냅샷을 만들고, 보존 기간이 지난 스냅샷을 지움
- 스냅샷 삭제는 즉시 끝나지만, 실제 오브젝트 정리는 OSD가 snaptrim으로 나중에 처리
- 64개 PG가 한꺼번에 snaptrim을 시작하면서 같은 OSD를 쓰는 클라이언트 I/O가 30~60초 밀림
🚫 흔한 처방이 안 먹는 이유
검색하면 거의 다 osd_snap_trim_sleep을 올리라고 합니다. 그런데 mClock에서는 이렇습니다.
$ ceph config show osd.11 | grep -E 'snap_trim_sleep|scrub_sleep'
osd_scrub_sleep 0.000000 override
osd_snap_trim_sleep 0.000000 override
osd_snap_trim_sleep_hdd 0.000000 override
osd_snap_trim_sleep_hybrid 0.000000 override
osd_snap_trim_sleep_ssd 0.000000 overridemClock은 자기가 직접 속도를 조절한다는 이유로 osd_snap_trim_sleep*과 osd_scrub_sleep을 0으로 강제합니다. 되살리려면 스케줄러를 wpq로 바꾸고 OSD를 전부 재시작해야 합니다.
그럼 mClock의 QoS 클래스로 snaptrim만 제한하면 되지 않을까요? 이것도 안 됩니다. mClock에서 backfill, scrub, snaptrim, PG 삭제가 전부 background_best_effort라는 한 클래스에 묶여 있습니다(recovery만 background_recovery로 따로). 이 클래스를 조이면 backfill과 scrub도 같이 느려집니다. 저는 노드를 정기적으로 재부팅해서 backfill이 자주 도는 클러스터라, 그쪽 성능은 건드리고 싶지 않았습니다.
🛠️ snaptrim에만 걸리는 손잡이 세 개
찾아보니 mClock과 상관없이 snaptrim에만 적용되는 설정이 있었습니다.
| 설정 | 기본 | 변경 | 의미 |
|---|---|---|---|
osd_pg_max_concurrent_snap_trims | 2 | 1 | PG 하나가 동시에 트림하는 오브젝트 수 |
osd_max_trimming_pgs | 2 | 1 | OSD 하나에서 동시에 트림하는 PG 수 |
osd_snap_trim_cost | 1 MiB | 250 MiB (이후 500 MiB) | mClock이 트림 1건에 매기는 비용 |
핵심은 세 번째입니다. mClock은 작업마다 “비용”을 매기고, 클래스 예산 안에서 비용만큼 시간을 배분합니다. snaptrim 1건을 1MiB가 아니라 250MiB짜리 작업으로 치게 하면, 같은 best_effort 예산 안에서 snaptrim만 수백 배 비싸져서 천천히 돕니다. backfill(osd_recovery_cost 20MiB)과 scrub(osd_scrub_cost 50MiB)은 자기 비용 그대로라 영향이 없습니다.
ceph config set osd osd_pg_max_concurrent_snap_trims 1
ceph config set osd osd_max_trimming_pgs 1
ceph config set osd osd_snap_trim_cost 262144000 # 250 MiBceph config에 들어가니 OSD 재시작 없이 바로 적용되고, 재부팅해도 유지됩니다.
📈 비용은 계단식으로 올렸다
한 번에 큰 값을 넣지 않고 50 → 150 → 250 MiB로 올리면서 매시 :15를 지켜봤습니다. 비용을 너무 높이면 트림이 한 시간 안에 안 끝나서 다음 스냅샷 정리와 겹치기 때문입니다. 특히 스냅샷을 한꺼번에 많이 지운 직후가 위험합니다.
| 변경 전 | 변경 후 (250 MiB) | |
|---|---|---|
| :15 이후 | 클라이언트 I/O 30~60초 막힘 | 트림이 60~165초 동안 천천히 진행 |
| SLOW_OPS | 매시간 | 0 |
| MDS_SLOW_METADATA_IO | 매시간 | 0 |
| OSD 호스트 CPU/load | — | 거의 변화 없음 |
| Ceph 위 서비스 (Nextcloud “not ready”, VM iowait) | 매시간 | 사라짐 |
트림이 걸리는 시간은 오히려 길어졌는데, 그게 정확히 원하던 겁니다. 같은 일을 더 길게 펴서 클라이언트가 못 느끼게 만든 거죠.
추가 (2026-10-14): 이후 비용을 500 MiB까지 올려서 운영하고 있습니다. 바꾼 뒤 일주일치 로그(10/7~10/14)를 다시 훑어봤는데 :15 언저리 SLOW_OPS는 한 건도 없었습니다. 그 기간에 딱 한 번 난 SLOW_OPS는 OSD가 내려간 별개 사건이었습니다.
↩️ 되돌리는 법
ceph config rm osd osd_pg_max_concurrent_snap_trims
ceph config rm osd osd_max_trimming_pgs
ceph config rm osd osd_snap_trim_costceph config rm은 키를 한 번에 하나씩만 받습니다.
📝 정리
- 시계처럼 정확한 장애는 cron부터 의심합니다. 이번엔 CephFS 스냅샷 보존 정리가 일으킨 snaptrim 폭주가 OSD를 30~60초씩 붙잡았습니다.
- 앱(Nextcloud)이 먼저 아파 보여도 같은 시각 다른 풀(RBD)까지 막혔다면 원인은 스토리지 쪽입니다.
- mClock에서는
osd_snap_trim_sleep이 0으로 강제됩니다. 흔한 처방이 안 먹는 이유입니다. - mClock은 snaptrim, backfill, scrub을 한 클래스로 묶어서, QoS 클래스로는 snaptrim만 따로 못 조입니다.
- 대신
osd_snap_trim_cost를 올리면 같은 예산 안에서 snaptrim만 비싸집니다. 동시성 두 개(osd_pg_max_concurrent_snap_trims,osd_max_trimming_pgs)를 1로 같이 내리면 효과가 확실합니다. - 비용은 계단식으로 올리고, 트림이 다음 주기 전에 끝나는지 꼭 확인합니다.

답글 남기기