KahaDB의 'db.data' 파일과 'db-*.log' 파일의 관계는?
ActiveMQ Classic을 프로덕션 환경에서 운영하다 보면 데이터 디렉토리(data/kahadb) 내부에 정체를 알 수 없는 파일들이 쌓여있는 것을 보게 됩니다. 그중에서도 시스템의 심장 역할을 하는 두 핵심 파일이 바로 db.data와 숫자가 붙어 증가하는 db-*.log 파일들입니다.
KahaDB는 관계형 데이터베이스(RDBMS)처럼 무겁지 않으면서도 메시지의 영속성(Persistence)과 초고속 입출력을 보장하기 위해, 데이터의 '저장'과 '검색' 역할을 물리적인 두 개의 파일 시스템으로 완벽하게 분리하는 아키텍처를 채택했습니다.
이 가이드에서는 두 파일이 각각 어떤 역할을 수행하며, 브로커 내부에서 메시지를 처리할 때 어떻게 상호작용하여 극강의 성능을 이끌어내는지 상세히 해부합니다.

1. 원본 데이터의 안식처: 'db-*.log' (저널 파일)
db-1.log, db-2.log 형태로 번호가 증가하며 생성되는 이 파일들은 메시지의 본문(Payload)과 메타데이터가 실제 기록되는 저널(Journal) 파일입니다.
- Append-Only (순차적 쓰기 전용):
저널 파일의 가장 큰 특징은 데이터를 오직 파일의 맨 끝에 이어 붙이기만(Append) 한다는 점입니다. 디스크의 헤드를 이리저리 움직일 필요 없이 순차적으로 기록하므로, 일반적인 파일 시스템이나 RDBMS의 랜덤 쓰기(Random Write)와는 비교할 수 없을 정도로 빠른 디스크 I/O 속도를 자랑합니다. - 롤링(Rolling) 방식의 생성:
하나의 로그 파일이 무한히 커지는 것을 막기 위해, 파일 크기가 설정된 최대치(기본값 32MB)에 도달하면 브로커는 기존 파일을 닫고 번호가 1 증가한 새로운 로그 파일(db-2.log,db-3.log...)을 생성하여 쓰기를 이어갑니다. - 불변성(Immutability):
한 번 기록된 로그 파일의 내용은 절대 중간에 수정되거나 부분 삭제되지 않습니다. 컨슈머가 메시지를 소비(Consume)하여 삭제해야 하는 상황이 오더라도, 저널 파일에서 해당 메시지를 지우는 것이 아니라 "이 메시지는 소비되었음(ACK)"이라는 새로운 마킹 기록을 저널의 끝에 추가할 뿐입니다.
2. 고속 탐색을 위한 나침반: 'db.data' (인덱스 파일)
저널 파일이 순차 쓰기에 특화되어 쓰기 속도는 빠르지만, 반대로 특정 메시지를 찾아서 읽어오려면 수십 개의 로그 파일을 처음부터 끝까지 다 뒤져야 하는 치명적인 단점이 생깁니다. 이 탐색 문제를 해결하기 위해 존재하는 것이 바로 db.data 파일입니다.
- B-Tree 자료구조:
db.data는 관계형 데이터베이스의 인덱스와 동일한 B-Tree(Balanced Tree) 구조로 이루어진 파일입니다. - 포인터(Pointer) 보관:
이 파일 안에는 무거운 메시지 본문이 들어있지 않습니다. 대신 "특정 목적지(Queue/Topic)의 N번째 메시지가 몇 번 로그 파일(db-*.log)의 어느 위치(Offset)에 기록되어 있는지"를 가리키는 아주 가벼운 참조 포인터만 저장됩니다. - 초고속 읽기 지원:
컨슈머가 큐에서 메시지를 요청하면, 브로커는 무거운 저널 파일을 뒤지지 않고 힙 메모리에 캐싱된db.data인덱스를 0.001초 만에 탐색합니다. 그리고 정확히 몇 번 파일의 어느 오프셋으로 가야 하는지 알아낸 뒤, 디스크에서 해당 부분만 쏙 빼서 컨슈머에게 전달합니다.
3. 두 파일의 라이프사이클과 상호작용 매커니즘
생산자(Producer)가 메시지를 보내고 소비자(Consumer)가 이를 처리하는 전체 라이프사이클 동안, 두 파일은 톱니바퀴처럼 맞물려 돌아갑니다.
- 메시지 인입 (Write): 생산자가 메시지를 보냅니다. 브로커는 즉시
db-*.log파일의 맨 끝에 메시지 본문을 순차적으로 기록합니다. 기록이 완료되면, 해당 디스크 위치 값을db.data인덱스 트리에 추가합니다. - 메시지 소비 (Read): 컨슈머가 메시지를 요청합니다. 브로커는
db.data인덱스를 조회하여 위치를 찾고,db-*.log파일에서 해당 메시지를 읽어 컨슈머에게 전달합니다. - 수신 확인 및 삭제 (ACK): 컨슈머가 처리를 완료하고 ACK를 보냅니다. 브로커는
db-*.log에 "메시지 처리 완료" 마커를 순차적으로 하나 더 적습니다. 그리고 가장 중요한 작업으로,db.data인덱스 트리에서 해당 메시지의 포인터(노드)를 완전히 삭제해 버립니다.
4. KahaDB 가비지 컬렉션(GC)의 핵심 원리
컨슈머가 메시지를 다 소비했음에도 불구하고 data 디렉토리에 db-*.log 파일들이 수십 개씩 쌓여서 디스크 용량을 차지하는 것을 볼 수 있습니다. 이는 KahaDB의 가비지 컬렉션(GC) 동작 방식 때문입니다.
저널 파일(db-*.log)은 불변성을 가지므로 내부의 메시지 하나가 소비되었다고 해서 그 부분만 도려낼 수 없습니다.
- 브로커는 주기적으로 백그라운드 GC 스레드를 실행하여
db.data인덱스를 스캔합니다. - 특정 저널 파일(예:
db-5.log) 안에 들어있는 10만 개의 메시지가 모두 소비되어,db.data인덱스 파일 내에db-5.log를 가리키는 포인터가 단 한 개도 남아있지 않게 되는 그 순간! - 브로커는 더 이상 쓸모가 없어진
db-5.log파일을 파일 시스템에서 통째로 삭제(또는 아카이브 디렉토리로 이동)하여 디스크 공간을 회수합니다. - 단, 파일 내에 단 1개의 메시지라도 컨슈머가 읽지 않은 채(또는 DLQ에 빠진 채) 남아있다면, 포인터가 살아있으므로 해당
db-*.log파일은 절대 삭제되지 않습니다.
5. 인덱스 손상(Corruption)과 복구 매커니즘
서버의 전원이 갑자기 나가거나 디스크 장애가 발생하면 db.data 인덱스 파일이 깨지는(Corruption) 경우가 발생할 수 있습니다.
하지만 크게 걱정할 필요는 없습니다. 데이터의 진짜 원본은 언제나 안전하게 순차 기록된 db-*.log 파일에 들어있기 때문입니다.
만약 브로커를 재시작했는데 db.data 파일이 깨져서 읽을 수 없다면, 브로커는 깨진 db.data 파일을 지우거나 백업한 뒤 복구 모드에 돌입합니다.
브로커는 1번 db-*.log 파일부터 마지막 파일까지 처음부터 끝까지 순차적으로 다시 읽어 들이면서, 아직 소비되지 않은 메시지들을 찾아내어 db.data 인덱스 트리를 백지상태에서 완벽하게 재구축(Rebuild)합니다. 로그 파일의 크기가 수십 GB에 달한다면 재구축에 수 분~수십 분이 걸릴 수 있지만, 데이터 유실 없이 시스템을 안전하게 복구할 수 있는 강력한 복원력을 제공합니다.
요약하자면, KahaDB는 db-*.log를 통해 디스크 I/O 성능과 데이터 원본의 안전성을 확보하고, db.data를 통해 메모리 기반의 초고속 라우팅 성능을 제공하는 완벽한 분업 아키텍처입니다. 이 두 파일의 관계를 이해하는 것은 브로커의 디스크 병목을 트러블슈팅하는 가장 중요한 첫걸음입니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| KahaDB 인덱스 복구(Cleanup) 시 발생하는 성능 지연 시간은? (0) | 2026.03.22 |
|---|---|
| KahaDB가 인덱스를 위해 B-Tree를 사용하는 이유는? (0) | 2026.03.22 |
| 'Temporary Queue'의 생명주기와 브로커 재시작 시의 동작은? (0) | 2026.03.22 |
| 브로커의 'Global Max Size' 도달 시 메시지 처리 거부 방식은? (0) | 2026.03.21 |
| Artemis의 'Core Bridge'가 가진 고성능 전송 메커니즘은? (0) | 2026.03.21 |