현재 getIndexes() 결과를 보니 분석할 수 있는 아주 중요한 단서들이 모두 나왔습니다. 맨 아래에 생성하신 { kind: 1, ts: 1 } 복합 인덱스가 정상적으로 반영되어 있습니다. 하지만 이 인덱스가 잡혀있음에도 불구하고 삭제가 157초씩 걸렸던 명확한 원인과, 현재 컬렉션 구조에 내포된 결정적인 모순(비효율) 2가지 를 짚어드리겠습니다. 1. 157초씩 걸린 진짜 원인 분석 ① 세 번째 인덱스: idx_createdAt_ttl (중복 삭제 오버헤드) JavaScript { v: 2, key: { createdAt: 1 }, name: 'idx_createdAt_ttl', background: true, expireAfterSeconds: 5184000 // 60일(60 * 60 * 24 * 60) } 상태: 현재 이 컬렉션은 데이터가 생성된 지 60일이 지나면 MongoDB가 자동으로 데이터를 삭제하도록 TTL(Time-To-Live) 인덱스 가 이미 설정되어 있습니다. 문제점: 그런데 삭제 데몬(프로그램)이 코드 내에서 ts: { '$lt': 1783878000 } 조건으로 데이터를 별도로 강제 삭제( remove )하고 있습니다. 즉, 데이터베이스 자체 시스템 스레드가 TTL 조건에 따라 내부적으로 지우는 작업과, 외부 프로그램이 직접 쿼리를 던져 지우는 작업이 동시다발적으로 얽히면서 ZFS 쓰기 락(Lock)을 가중시키고 있습니다. ② 두 번째 인덱스: host_1_kind_1_ts_-1 (인덱스 수정 오버헤드) 상태: 대량 삭제( remove ) 연산이 발생하면, MongoDB는 단순히 데이터만 지우는 게 아니라 해당 문서가 등록되어 있던 모든 인덱스 트리에서도 데이터를 함께 삭제(Rebalancing)해야 합니다. 문제점: 현재 host_1_kind_1_ts_-1 이라는 큰 복합 인덱스가 걸려있기 때문에, 데이터를 한 번에 수만~수백만 건씩 limit: 0 으로 밀어버리면 이 인덱스 구조를 재정렬하느라 스레드가 풀 가동되고 zio->i (ZFS I/O) 병목이 발생해 157초 동안 멈추는 현상이 일어납니다. 2. 부하를 근본적으로 완전히 해결하는 방안 방안 A: 외부 삭제 로직을 끄고 TTL에 완전히 위임하기 (가장 추천) 이미 createdAt 기준 60일 자동 삭제 인덱스가 존재하므로, 크론탭이나 외부 데몬 프로그램 소스 코드에서 강제로 remove (또는 deleteMany ) 쿼리를 날리는 배치 로직을 아예 주석 처리하거나 제거 하는 것이 가장 좋습니다. 자동으로 삭제되도록 인덱스를 설계해 두고 외부에서 또 지우는 중복 작업을 차단하는 것만으로도 전체 CPU 부하의 80% 이상이 즉시 사라집니다. 방안 B: 외부 삭제 로직이 반드시 유지되어야 하는 경우 ( limit 분할 락 방지) 만약 비즈니스 로직상 특정 시점에 무조건 정확하게 지워야 해서 외부 코드를 유지해야 한다면, 이전 단계에서 말씀드린 대로 한 번에 다 지우는 방식을 버리고 5,000건씩 쪼개서 지우도록 코드를 전면 개편 해야 합니다. 인덱스( kind_1_ts_1 )가 이미 잡혀있기 때문에, 대량 삭제 락을 방지하기 위해 limit: 5000 옵션을 걸고 루프를 돌려주면 인덱스 재정렬 오버헤드가 분산되어 top 화면에서 스레드가 튀는 현상이 완전히 사라집니다.