본문 바로가기
1. 개발/1.8. ActiveMQ

KahaDB의 인덱스 파일(db.data) 크기 제한과 분할 방법은?

by 엉짱 2026. 4. 4.
반응형

KahaDB의 인덱스 파일(db.data) 크기 제한과 분할 방법은?

메시지 브로커(ActiveMQ)의 KahaDB 스토리지 엔진을 운영할 때, 모든 엔지니어의 시선은 끝없이 늘어나는 저널 파일(db-*.log)에 쏠려 있습니다. 하지만 시스템의 진정한 '뇌'이자, 브로커의 전체 성능을 좌우하는 가장 치명적인 아킬레스건은 바로 단 하나 존재하는 인덱스 파일, db.data입니다.

저널 파일이 순차적으로 데이터를 무식하게 쌓아두는 창고라면, db.data는 수백만 개의 메시지가 어느 창고의 몇 번째 선반에 있는지 정확히 추적하는 B-Tree 자료구조의 지도입니다. 이 지도가 무한정 커지면 브로커는 길을 잃고 멈춰 섭니다.

이 가이드에서는 db.data 파일이 거대해질 때 발생하는 인프라의 물리적 한계와, 이를 여러 개의 독립적인 인덱스로 쪼개어 브로커의 처리량을 극대화하는 'Multi-KahaDB (mKahaDB)' 분할 아키텍처를 상세히 해부합니다.


1. 단일 'db.data' 파일의 비대화와 물리적 한계

KahaDB는 기본적으로 시스템 내의 모든 큐(Queue)와 토픽(Topic)에 대한 인덱스 정보를 단 하나의 db.data 파일에 몰아넣습니다. 브로커에 물리적인 파일 크기 상한선(Hard Limit)은 존재하지 않지만, 파일이 수 기가바이트(GB) 단위로 커지면 다음과 같은 세 가지 구조적 붕괴가 시작됩니다.

A. 인덱스 캐시(indexCacheSize)의 적중률 하락
브로커는 빠른 라우팅을 위해 db.data의 일부를 JVM 힙 메모리(indexCacheSize, 기본값 10000 페이지)에 올려두고 사용합니다.
인덱스 파일이 작을 때는 전체 B-Tree가 메모리에 쏙 들어가지만, 큐에 메시지가 수백만 건 쌓여 db.data 파일이 메모리 캐시 크기를 초과하게 되면 끔찍한 일이 발생합니다. 브로커가 메시지를 찾을 때마다 메모리 캐시에 없는 노드(Node)를 물리적 디스크에서 직접 읽어와야 하므로(Page Fault), 극심한 디스크 I/O Wait가 발생하며 라우팅 속도가 수백 배 느려집니다.

B. 글로벌 락(Global Lock) 경합의 심화
수십 개의 큐에서 동시에 메시지가 들어오고 나갈 때, 모든 스레드는 단 하나의 db.data 파일을 수정하기 위해 배타적 락(Write Lock)을 두고 치열하게 경쟁해야 합니다. 특정 큐 하나에 트래픽이 폭주하여 인덱스 쓰기 락을 독점하면, 전혀 상관없는 다른 큐들의 메시지 전송 스레드까지 전부 대기(Blocking) 상태에 빠지는 치명적인 간섭 현상이 발생합니다.

C. 브로커 재기동 및 복구(Recovery) 시간의 장기화
서버가 비정상 종료된 후 재기동될 때, 브로커는 db.redo 로그를 바탕으로 db.data의 정합성을 검증하고 복원해야 합니다. 인덱스 파일이 수 GB에 달한다면 이 복구 과정에만 수십 분이 소요되어 뼈아픈 서비스 다운타임을 초래합니다.


2. 궁극의 아키텍처 해결책: mKahaDB (Multi-KahaDB) 도입

단일 인덱스의 한계를 돌파하기 위해 ActiveMQ가 제공하는 가장 강력한 스토리지 튜닝 기법이 바로 'mKahaDB'입니다.

mKahaDB의 핵심 사상은 "모든 큐를 하나의 바구니에 담지 말고, 목적지(Destination)의 패턴에 따라 저널 파일과 인덱스(db.data) 파일을 독립적인 여러 개의 KahaDB 인스턴스로 물리적으로 쪼개자"는 것입니다.

mKahaDB 분할의 3대 이점:

  1. 완벽한 I/O 격리: 주문 큐(Order)와 로그 큐(Log)가 서로 다른 db.data를 가지게 되므로, 락(Lock) 경합이 완전히 사라집니다. 로그 큐에 트래픽 폭탄이 떨어져도 주문 큐의 성능은 100% 방어됩니다.
  2. 캐시 효율의 극대화: 인덱스가 잘게 쪼개지므로, 각 KahaDB 인스턴스의 B-Tree 크기가 획기적으로 줄어들어 대부분의 인덱스가 메모리 캐시(indexCacheSize) 내에 쾌적하게 상주하게 됩니다.
  3. 부분적인 가비지 컬렉션(GC): 특정 큐의 메시지가 모두 소비되면, 해당 큐 전용의 KahaDB 디렉토리만 통째로 비워지므로 디스크 공간 회수 속도가 압도적으로 빨라집니다.

3. mKahaDB 스토리지 분할 설정 가이드

단일 KahaDB를 mKahaDB 아키텍처로 마이그레이션하려면 activemq.xml<persistenceAdapter> 블록을 전면 수정해야 합니다.

다음은 트래픽이 가장 무거운 '결제(payment)' 관련 큐들만 별도의 물리적 인덱스로 빼내고, 나머지는 기본 인덱스를 사용하도록 쪼개는 실무 설정 예시입니다.

<persistenceAdapter>
    <mKahaDB directory="${activemq.data}/kahadb">
        <filteredPersistenceAdapters>

            <filteredKahaDB queue="queue.payment.>">
                <persistenceAdapter>
                    <kahaDB journalMaxFileLength="32mb" 
                            indexCacheSize="10000" />
                </persistenceAdapter>
            </filteredKahaDB>

            <filteredKahaDB queue="queue.inventory.>">
                <persistenceAdapter>
                    <kahaDB journalMaxFileLength="32mb" />
                </persistenceAdapter>
            </filteredKahaDB>

            <filteredKahaDB>
                <persistenceAdapter>
                    <kahaDB journalMaxFileLength="32mb" />
                </persistenceAdapter>
            </filteredKahaDB>

        </filteredPersistenceAdapters>
    </mKahaDB>
</persistenceAdapter>

설정 시 반드시 알아야 할 메커니즘:

  • 브로커를 기동하면 지정된 데이터 폴더 하위에 queue.payment. 전용 폴더, queue.inventory. 전용 폴더 등이 각각 개별적으로 자동 생성됩니다.
  • 각 폴더 안에는 자신만의 독립적인 db.data (인덱스)와 db-*.log (저널) 파일들이 완전히 분리되어 생성되고 관리됩니다.

4. mKahaDB 운영 시의 아키텍처 트레이드오프

인프라 시스템에서 모든 것을 분할하는 것이 무조건 정답은 아닙니다. mKahaDB를 도입할 때 엔지니어가 감수해야 할 대가가 존재합니다.

A. 파일 디스크립터(File Descriptor) 고갈 위험
큐를 100개 단위로 세밀하게 쪼개어 mKahaDB 인스턴스를 100개 띄운다면, 각 인스턴스가 저널 파일과 인덱스 파일을 열어두어야 하므로 OS 레벨의 오픈 파일 개수가 폭증합니다. 리눅스의 ulimit -n 값을 최소 수십만 단위 이상으로 넉넉하게 프로비저닝하지 않으면 'Too many open files' 에러와 함께 브로커가 즉사할 수 있습니다.

B. JVM 힙 메모리(Heap Memory) 사용량 증가
인덱스가 10개로 쪼개졌다는 것은, JVM 메모리에 상주하는 B-Tree 객체 인스턴스와 캐시 영역(indexCacheSize) 역시 10배로 늘어났음을 의미합니다. 무분별한 분할은 브로커의 힙 메모리를 빠르게 고갈시키므로, 분할된 각 <kahaDB> 블록마다 메모리 크기를 세밀하게 재조정해야 합니다.

C. XA 트랜잭션(분산 트랜잭션)의 제약
만약 클라이언트가 여러 개의 서로 다른 큐를 하나의 분산 트랜잭션(2PC)으로 묶어서 전송하려 할 때, 이 큐들이 서로 다른 mKahaDB 인스턴스에 속해 있다면 트랜잭션 복구 로직이 극도로 복잡해지거나 성능 저하가 발생할 수 있습니다. 밀접하게 연관된 도메인의 큐들은 가급적 동일한 인스턴스에 묶이도록(Wildcard 사용) 설계해야 합니다.


5. 결론 및 최적화 전략

단일 db.data 인덱스 파일은 장난감 프로젝트에서는 훌륭하게 동작하지만, 트래픽이 집중되는 엔터프라이즈 인프라에서는 반드시 쪼개어내야 할 거대한 폭탄과도 같습니다.

무작정 모든 큐를 쪼개는 것보다는, 전체 디스크 I/O의 80퍼센트를 점유하는 상위 1~2개의 '헤비 트래픽 큐'나, 메시지 크기가 비정상적으로 큰 '대용량 전용 큐'만을 선별하여 mKahaDB로 분리(Isolation)해 내는 것이 가장 우아하고 효과적인 아키텍처 설계입니다. 비즈니스 도메인의 트래픽 패턴을 정확히 분석하여 브로커의 뇌를 가장 효율적으로 분할하시기 바랍니다.

반응형