🌿 jadelog
🤔 Database

MongoDB WiredTiger의 파일 구조

status
Public
date
Dec 6, 2025
slug
mongodb-wiredtiger-file-structure
summary
제조 데이터 실무 흐름부터 데이터 거버넌스 프레임워크, 파이프라인 자동화 및 클라우드 ETL 실습까지 데이터 엔지니어링의 핵심 과정을 상세히 정리했습니다. 실무 적용 가이드와 함께 데이터 품질 진단 및 표준화 노하우를 확인해 보세요.
type
Post
category
🤔 Database
tags
WiredTiger File Structure NoSQL Performance Optimization Tech Blog 글 읽기
Database
thumbnail
series
Tech Blog InQuery
tech.kakao.comtech.kakao.comMongoDB WiredTiger의 파일 구조 - tech.kakao.com
회사에서 몽고디비를 사용하는 것도 아닌데 왜 계속 몽고디비에 대해 공부하고 있냐고 한다면…
  1. 이전에 몽고디비를 사용한 적 있고
  1. 앞으로 몽고디비를 잘 활용하는 서비스에 기여해보고 싶다는 생각을 하고 있고
  1. 그리고 디비 구조에 대해서 하나 깊게 알아두면 다른 디비 볼 때에도 도움이 될 수 있으리라 생각해서
 
걍 뭐 숙제도 아니니까 내가 공부해보고 싶은 영역 공부해보는거니까…
글케 정리하고 시작

tech.kakao.comtech.kakao.comMongoDB - tech.kakao.com 시리즈 글 1번째
MongoDB의 스토리지 엔진인 WiredTiger에 대한 궁금증을 해결해보자.
WiredTiger가 관리하는 파일들의 상세 내용에 대해 알아보자.
대부분 사람이 읽을 수 없는 형태로 되어 있기는 함
MongoDB에서 삽입하고 갱신한 데이터가 파일에는 어떤 구조로 저장되며 관리되고 있는지 확인
 
+) MongoDB의 WiredTiger 스토리지 엔진 설명
  • WiredTiger는 MongoDB의 기본 스토리지 엔진으로, 2014년 MongoDB 3.0 버전부터 도입
    • +) Ver 8.0.6이 20250319에 릴리스
  • 기존 MMAPv1 엔진 대비 뛰어난 성능, 동시성, 내구성, 효율성을 제공하며, 현재 MongoDB Atlas, Enterprise, Community 버전 모두에서 기본 엔진으로 사용됨
 
[주요 특징 및 작동 원리]
  • 문서 수준 동시성 제어
    • WiredTiger는 문서(document) 단위의 동시성 제어를 지원합니다. 여러 클라이언트가 하나의 컬렉션 내에서 서로 다른 문서를 동시에 수정할 수 있어 높은 동시성을 제공
    • 대부분의 읽기/쓰기 작업에서 낙관적 동시성 제어(Optimistic Concurrency Control)를 사용
      • 충돌이 발생하면 MongoDB가 자동으로 해당 작업을 재시도
      • +) Optimistic Concurrency Control; 낙관적 동시성 제어
        여러 사용자가 동시에 데이터를 수정할 때 충돌이 자주 발생하지 않을 것이라고 가정하는 동시성 관리 방식
        • 이 방식은 데이터에 대한 잠금(lock)을 사용하지 않고, 데이터 수정 시점에만 충돌 여부를 검사 → 다수의 클라이언트가 동시에 작업 가능을 지원하기 위함
         
  • 멀티버전 동시성 제어(MVCC)
    • WiredTiger는 MVCC(Multi-Version Concurrency Control) 방식을 도입해, 트랜잭션이 시작될 때 데이터의 스냅샷을 제공
      • 이를 통해 읽기 작업은 쓰기 작업에 방해받지 않고 일관된 데이터를 조회할 수 있습니다
    • 각 문서에 대해 여러 버전을 동시에 관리하며, 필요한 시점의 데이터를 효율적으로 제공
  • 트랜잭션 및 ACID 보장
    • WiredTiger는 트랜잭션을 지원하며, 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability) 등 ACID 특성 보장
    • 세션 단위 트랜잭션 관리와 세밀한 잠금 전략을 통해 높은 동시성과 데이터 무결성을 동시에 보장
  • Write-Ahead Logging(WAL)과 체크포인트
    • 모든 데이터 변경 사항은 먼저 트랜잭션 로그(WAL)에 기록되어, 장애 발생 시 데이터 복구가 가능
      • +) Write-Ahead Logging
        1. 데이터베이스에 변경(삽입, 수정, 삭제) 작업이 발생하면, 해당 변경 내용을 먼저 WAL(로그 파일)에 순차적으로 기록
        1. 로그에 기록이 완료되어야만 실제 데이터 파일에 변경이 반영
        1. 만약 시스템에 장애(예: 전원 장애, 서버 다운)가 발생해도, 로그를 이용해 장애 발생 시점까지의 변경 사항을 모두 복구 가능
         
        +) Write-Ahead Logging이 성능에 미치는 영향
        1. 디스크 I/O 감소 및 쓰기 성능 향상
          1. 데이터 변경 시마다 바로 디스크의 데이터 파일을 수정하는 대신, 변경 내용을 우선 순차적으로 로그 파일(WAL)에 기록 → 로그 파일은 순차적(Sequential)으로 기록되기 때문에, 임의(Random) 접근보다 디스크 헤더의 움직임이 적고 훨씬 빠름
            • 실제로 순차 접근은 랜덤 접근보다 최대 100배까지 빠름
            • 이렇게 하면 디스크 I/O가 빈번하게 발생하는 것을 방지하여 전체적인 쓰기 성능을 크게 향상
        1. 쓰기 작업 증가에 따른 부하
          1. WAL은 변경 내용을 로그와 데이터 파일 모두에 기록하므로, 쓰기 작업이 많은 환경에서는 디스크 쓰기량이 증가
            <> 대부분의 경우, 순차적 로그 기록의 효율성이 무작위 데이터 파일 쓰기의 성능 저하를 상쇄
        1. 데이터 일관성 및 내구성 보장
          1. 트랜잭션의 변경 사항이 로그에 먼저 안전하게 기록되므로, 장애 발생 시에도 로그를 이용해 데이터베이스를 복구 가능
        1. 데이터 파일 반영(Flush) 지연 및 배치 처리
          1. WAL을 사용하면 데이터 변경이 누적될 때까지 메모리 버퍼에 저장하고, 일정량이 쌓이면 한 번에 데이터 파일로 반영(Flush) 가능
            • 이로 인해 데이터 파일에 대한 무작위 쓰기가 줄어들고, 배치로 처리되어 효율성이 높음
        1. 로그 파일 관리의 중요성
          1. 로그가 과도하게 쌓이면 로그 파일의 크기가 커지고, I/O 부하가 증가하여 오히려 성능 저하 발생 → 주기적으로 Checkpoint를 설정해 불필요한 로그를 정리하고, 백업 및 관리가 필요
       
    • 일정 주기(기본 60초)마다 체크포인트를 생성하여, 인메모리 변경 사항을 디스크에 일관되게 반영
      • 체크포인트는 복구 지점 역할을 하며, 장애 시 마지막 체크포인트까지의 데이터는 항상 일관성을 보장
  • 인메모리 캐시 및 성능
    • WiredTiger는 데이터와 인덱스를 메모리 캐시에 적재하여 빠른 쿼리 성능을 제공 → 변경된 데이터는 캐시 내에서 관리되며, 디스크로의 기록은 배치로 처리되어 효율적
    • 캐시는 mongod 인스턴스 단위로 할당, 컬렉션별로 따로 할당되지 않음
  • 데이터 구조
    • 데이터는 주로 B-Tree 구조로 저장, 데이터 파일과 별도의 저널 로그 파일을 통해 데이터 무결성과 복구를 지원
  • 확장성과 효율성
    • 멀티코어 확장성, lock-free 알고리즘, hazard pointers 등 최신 기술을 도입해 고성능과 확장성을 동시에 달성
 

WiredTiger Storage Engine

notion image
  • 스토리지 엔진은 메모리와 디스크에 모두 데이터가 저장되는 방식을 관리하는 등 몽고디비에서 핵심적인 역할을 수행
  • 플러그형 스토리지 엔진 아키텍처를 지원해서 다양한 스토리지 엔진을 지원했지만 현재는 WiredTiger 스토리지 엔진을 기본 스토리지 엔진으로 상요하고 있음
  • WiredTiger
    • 트랜잭션 지원
    • Lock Free 알고리즘
    • 데이터 압축
    • key-value store
 
+) WiredTiger처럼 메모리(인메모리 캐시)와 디스크(블록 매니저)에 모두 데이터를 저장하는 방식은 매우 대중적이고 현대적인 데이터베이스 스토리지 엔진의 표준적인 구조
 
[메모리-디스크 하이브리드 구조를 사용하는 이유]
  • 메모리는 매우 빠르지만 용량이 한정되어 있고, 휘발성입니다.
  • 디스크는 느리지만 대용량 데이터를 영구적으로 저장할 수 있습니다.
  • 따라서, 자주 접근하는 데이터나 최근 변경된 데이터는 메모리에 두고, 전체 데이터의 일관성과 내구성은 디스크에 저장함으로써 성능과 안정성을 모두 확보
 

분석 도구

  • BSON; Binary JSON
    • 몽고디비에서 데이터를 저장할 때 사용하는 표준 포맷
    • 바이너리 형태로 이뤄져있음
    • 이를 읽기 위해 bsondump 를 통해 JSON 형식으로 변환
      • wt 도 있음. 이는 WiredTIger 소스코드에서 직접 빌드해야 함
 

WiredTiger에서 관리하는 파일 구조

WiredTiger

  • WiredTiger 파일은 텍스트 파일
  • 현재 실행 중인 WiredTiger 스토리지 엔진의 버전 정보를 저장하고 있음
  • MongoDB와 WiredTiger 버전 관계
    • WiredTiger의 변경 사항은 MongoDB에도 큰 변화를 가져옴
 

WiredTiger.lock

  • 다른 MongoDB 서버 인스턴스가 데이터 파일들에 동시에 접근하는 것을 방지
  • 서버가 정상적으로 종료되었는지 여부를 판단하는 역할
    • 서버가 시작될 때, WiredTiger.lock 파일의 존재 여부를 기반으로 시스템 복구가 결정
      • 비정상 종료 후 임의로 이 파일을 삭제할 경우, 데이터 유실의 위험이 있음
    • 마지막 안정된 체크포인트 시점으로 데이터 복구를 진행
 
+) 몽고디비와 lock 사용 정리하면
  • 일반적인 데이터 접근(읽기/쓰기)에는 lock을 거의 사용하지 않음(문서 수준 동시성, 낙관적 제어)
  • 서버 복구나 중복 프로세스 방지 등 데이터베이스 무결성 보장을 위해서는 WiredTiger.lock 파일이 사용
 

WiredTiger.wt

  • WiredTiger에서 관리하는 모든 파일의 구성 정보와 최신 체크포인트 정보 등의 메타 데이터를 저장하고 있음
  • 디스크에 저장된 B+tree 구조나 컬렉션에 사용되는 설정 정보는 file: 항목 에 저장되어 있음
 
wt dump file:WiredTiger.wt | tail -n +7
> wt dump file:WiredTiger.wt | tail -n +7 ... // 주요 필드 : id=90, log=(enabled=false), checkpoint=(WiredTigerCheckpoint.1=(...) file:kakao/collection-0-6574883217453298519.wt access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=1),assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=snappy,cache_resident=false,checksum=on,collator="",columns="",dictionary=0,encryption=(keyid="",name=""),format=btree,huffman_key="",huffman_value="", id=90,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=q,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=64MB, log=(enabled=false),memory_page_image_max=0,memory_page_max=10m,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token="",bucket="",bucket_prefix="",cache_directory="",local_retention=300,name="",object_target_size=0),value_format=u,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none, checkpoint=(WiredTigerCheckpoint.1=(addr="018181e47b28f1be8281e41546bd168381e4d0a3d14f808080e22fc0cfc0",order=1,time=1731567505,size=8192,newest_start_durable_ts=7437025774726545409,oldest_start_ts=7437025688827199491,newest_txn=454,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=327610,run_write_gen=327608)),checkpoint_backup_info="",checkpoint_lsn=(42,298112) ... // 주요 필드 : id=92, log=(enabled=false), checkpoint=(WiredTigerCheckpoint.1=(...) file:kakao/index-2-6574883217453298519.wt access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=8),assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor="",cache_resident=false,checksum=on,collator="",columns="",dictionary=0,encryption=(keyid="",name=""),format=btree,huffman_key="",huffman_value="", id=92,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=16k,key_format=u,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=16k,leaf_value_max=0, log=(enabled=false),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=true,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token="",bucket="",bucket_prefix="",cache_directory="",local_retention=300,name="",object_target_size=0),value_format=u,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none, checkpoint=(WiredTigerCheckpoint.1=(addr="018181e4e4eb0e718281e41546bd168381e43128d539808080e22fc0cfc0",order=1,time=1731567531,size=8192,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=0,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=327610,run_write_gen=327608)),checkpoint_backup_info="",checkpoint_lsn=(42,321792) ... // 주요 필드 : id=18, log=(enabled=true), checkpoint=(WiredTigerCheckpoint.28698=(...) file:local/collection-2-9099766355459865316.wt access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=1,oplogKeyExtractionVersion=1),assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=snappy,cache_resident=false,checksum=on,collator="",columns="",dictionary=0,encryption=(keyid="",name=""),format=btree,huffman_key="",huffman_value="", id=18,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=q,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=64MB, log=(enabled=true),memory_page_image_max=0,memory_page_max=10m,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token="",bucket="",bucket_prefix="",cache_directory="",local_retention=300,name="",object_target_size=0),value_format=u,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none, checkpoint=(WiredTigerCheckpoint.28698=(addr="018d81e45e953f458e81e4615ccec2c0ae81e4905c78bc808080e401296fc0e4010c8fc0",order=28698,time=1731567549,size=17612800,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=0,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=327799,run_write_gen=327608)),checkpoint_backup_info="",checkpoint_lsn=(42,338048)
  1. log=(enable=false) vs. log=(enabled=true)
      • log 설정은 WiredTiger의 write-ahead log 기능인 journaling의 활성화 여부를 결정
        • +) Journaling: 예기치 못한 종료로부터 데이터의 내구성을 보장하기 위한 기능
      • <> 사용자가 생성한 컬렉션에 대한 데이터 파일과 인덱스 파일에는 log 설정이 비활성화됨 ← 성능을 위해서임 (MongoDB의 write durability는 대부분 oplog와 checkpointing에 의존)
        • 몽고디비는 oplog에 db의 모든 변경 사항을 저장하므로 oplog에 대한 journaling만으로도 복구할 수 있음
        • oplog는 가능하면 원본 데이터가 아니라 변경분만 저장하여 효율적이라는 장점도 있음
        • 성능을 위해서는 모든 파일에 대해 저널링을 하는 것보다, oplog에 대한 저널링만 선택하는 것이 합리적인 선택이라 생각
      • log 옵션이 활성화되지 않은 데이터 및 인덱스 파일에 대한 쓰기는 기본적으로 메모리에 유지되고, eviction 혹은 checkpoint를 통해서만 디스크로 쓰이며 checkpoint-durability 만을 보장
       
  1. id=90, id=92, id=18
      • 각 항목의 id값은 파일의 고유한 식별자
      • 다른 파일에서 참조할 때 일반적으로 파일의 이름이 아닌 여기에 설정된 file id를 사용
       
  1. checkpoint=(WiredTigerCheckpoint.XXXX)
      • 체크포인트는 예기치 못한 종료 시 복구할 수 있는 기준점으로 사용
      • 체크포인트가 파일 별로 수행되므로 각 파일은 서로 다른 체크포인트 정보를 저장할 수 있음
        • 체크포인트 완료 후 변경된 디스크의 루트 페이지 주소와 같은 메타데이터를 포함
 

WiredTiger.turtle

  • 메타데이터를 저장하는 WiredTiger.wt 파일의 최신 체크포인트 정보와 같은 메타데이터를 저장하는 중요한 파일
  • WiredTiger.wt 에 대한 메타데이터를 별도로 관리하는 이유
    • 각 파일의 체크포인트가 수행될 때마다 WiredTiger.wt 파일의 내용은 갱신
    • 이렇게 지속적으로 갱신되는데, 이 WiredTiger.wt 파일에 대한 체크포인트 또한 전체 체크포인트 과정의 마지막 단계에서 수행되어 이를 WiredTiger.turtle에 별도로 기록하는 것
    • 체크포인트 과정은 Snapshot isolation Transaction의 컨텍스트 내에서 실행, 체크포인트가 완료될 때까지 Consistent view를 제공
 

sizeStorer.wt

  • 각 컬렉션의 전체 도큐먼트 수와 데이터 사이즈를 저장하는 파일
    • 데이터를 추가하거나 수정할 때, _changeNumRecordsAndDataSize 함수를 통해 레코드의 수와 데이터 사이즈를 매번 갱신하며 관리
    • MongoDB에서 estimatedDocumentCount() 를 수행할 경우 이 파일을 참고
 
  • 연산이 실패했을 경우 롤백 시나리오는 고려되어 있지만 체크포인트 사이에 발생한 비정상 종료로 인한 작업까지는 고려하지 않음
    • 이 경우, 서버를 재시작한 후 validate() 명령을 통해 메타데이터를 최신 값으로 갱신
  • 샤드 클러스터 환경에서는 해당 파일에 저장된 값은 데이터 노드인 각각의 샤드에서 관리하는 정보이므로 Orphaned Document를 필터링할 수 있는 기능은 없음
    • +) Orphaned Document
      • 청크 마이그레이션에 실패했거나 비정상적인 종료로 인해 정리되지 못하고 이전 샤드에 그대로 남아있는 도큐먼트
    • estimatedDocumentCount() 에는 Orphaned Document가 포함된 결과를 반환
      • 필터링한 정확한 결과를 얻기 위해서는 mongos에서 SHARDING_FILTER 스테이지를 수행할 수 있는 countDocuments() 명령어를 사용
 
+) estimatedDocumentCount()countDocuments() 의 차이
구분
estimatedDocumentCount()
countDocuments()
정확성
대략적인(추정) 값
정확한 값
필터 지원
지원하지 않음 (전체 문서만)
쿼리 조건 지원 (필터링 가능)
동작 방식
컬렉션의 메타데이터(인덱스 통계 등)를 이용해 빠르게 추정
실제로 컬렉션을 스캔하여 조건에 맞는 문서 수 계산
성능
매우 빠름 (대용량 컬렉션도 빠름)
느릴 수 있음 (조건 복잡/컬렉션 대용량일수록 느려짐)
사용 예
대시보드, 빠른 개수 확인 등 대략적인 수치가 필요한 경우
조건에 맞는 정확한 문서 수가 필요한 경우
 

_mdb_catalog.wt

  • MongoDB 사용자에게 표시되는 컬렉션과 인덱스에 대한 메타데이터가 저장
 
+) 엥 그럼 사용자게에 표시되지 않는 컬렉션은?
MongoDB에서 일반 사용자에게 표시되지 않는 컬렉션은 주로 시스템 컬렉션(system collections)
이 컬렉션들은 <database>.system.* 네임스페이스를 사용하며, MongoDB 내부 동작 및 관리 목적으로만 사용
  • admin.system.roles: 사용자 정의 역할 정보를 저장
  • admin.system.users: 사용자 인증 정보와 역할 정보를 저장
  • admin.system.version: 내부 운영을 위한 메타데이터를 저장
  • config.system.indexBuilds: 진행 중인 인덱스 빌드 정보를 저장
  • config.system.preimages: 변경 스트림 옵션이 활성화된 컬렉션의 이전 문서 버전을 저장
  • <database>.system.buckets: 타임시리즈 컬렉션의 실제 데이터를 최적화된 포맷으로 저장
 
이러한 시스템 컬렉션은 일반적으로 show collections 명령이나 db.getCollectionNames() 등으로는 표시되지 않으며, 직접 접근하거나 수정하는 것은 권장되지 않음 → MongoDB는 이 컬렉션들을 내부적으로 관리하며, 사용자는 특별한 경우가 아니면 볼 필요도, 수정할 필요도 없음
 
md
몽고디비에서 사용하는 네임스페이스와 컬렉션의 설정, UUID 및 인덱스 정보와 같은 메타데이터
idxIdent(index identifier)
WiredTiger는 몽고디비 인덱스를 매핑된 식별자를 사용하여 관리 → _id 에 해당하는 인덱스 매핑 정보가 _mdb_catalog.wt 파일에서 관리되므로 실제 인덱스 파일에는 인덱스 값만을 포함하여 데이터 처리 효율을 높임
ns와 indent
컬렉션도 네임스페이스를 직접 사용하는 대신, 매핑된 식별자를 통해 관련 파일에 접근
 

WiredTigerHS.wt

  • WiredTiger에서 데이터의 변경 이력을 관리하기 위해 사용되는 히스토리 스토어(History Store)의 데이터를 관리
    • +) History Store
    • MVCC; Multi Version Concurrency Control 를 활용하여 여러 버전의 데이터 변경사항을 메모리에 유지
    • 변경사항은 Update-Chain이라고 불리는 Singly linked list에 저장됨
    • 새로운 변경사항이 발생할 경우, 업데이트 체인의 맨 앞에 추가되어, 가장 최근 업데이트가 맨 앞에 위치하게 됨
    •  
      +) 엥 왜 맨 뒤에 안 두지? git 처럼 차근차근 변경 내역 쌓아가면 롤백하기도 쉽지 않을까?
      맨 뒤에 추가한다면, 최신 데이터를 찾기 위해 체인을 끝까지 순회해야 하므로 비효율적
      히스토리 스토어(History Store)에서 새로운 변경사항이 발생하면 업데이트 체인의 맨 앞에 추가되는 이유는, 최신 데이터를 가장 빠르게 찾고 효율적으로 데이터 일관성과 동시성을 관리하기 위해서
      이 방식은 MongoDB WiredTiger뿐 아니라, 다수의 현대적인 DBMS에서 채택하고 있는 구조
      • 최신 버전 우선 접근
        • 대부분의 데이터베이스 작업(조회, 복구, MVCC 등)에서 가장 자주 필요한 것은 가장 최신의 데이터 버전 → 업데이트 체인의 맨 앞에 최신 변경사항을 추가하면, 데이터베이스가 최신 데이터를 빠르게 찾을 수 있음
      • MVCC(Multi-Version Concurrency Control) 지원
        • WiredTiger 등 현대적인 스토리지 엔진은 MVCC를 통해 여러 트랜잭션이 동시에 서로 다른 시점의 데이터를 읽을 수 있도록 함
      • 일관성 및 동시성 관리
        • 업데이트 체인에 새로운 버전을 맨 앞에 추가하면, 동시성 제어와 롤백, 복구 등 내부적인 관리가 단순해짐. 예를 들어, 롤백이 필요할 때도 최신 버전부터 차례로 이전 버전으로 되돌아가며 처리할 수 있음
 
업데이트 체인 (In-memory)
업데이트 체인 (In-memory)
  • 메모리 상의 데이터는 eviction 혹은 checkpoint 과정을 통해 디스크에 작성됨 → 메모리에 있는 여러 버전 중 하나의 버전만 디스크에 쓰임
    • 어떤 버전을 작성할 것인지 선택하는 과정: WiredTiger의 Reconciliation
      • notion image
  • 디스크에 쓰일 이미지가 선택되면 이전 변경사항들은 메모리 상에 제거되어야 함
    • WiredTiger는 Snapshot Isolation을 기본으로 사용하고 있기 때문에, 오래 수행되고 있는 트랜잭션이 존재하는 경우, 과거 버전에 대한 조회가 필요
    • 이전 변경 사항들이 단순히 메모리 상에서 제거된다면 조회를 실패하는 경우가 발생할 수 있으므로, Reconsilation 과정에서 선택한 디스크 이미지보다 과저 버전에 대한 변경 이력은 모두 히스토리 스토어에 저장
  • 결국 과거 버전의 변경사항을 저장하고 있다는 것
    • 수행 중인 트랜잭션이 메모리에서 적절한 버전의 데이털르 찾지 못할 경우, 이 파일에서 데이터를 조회할 수 있음
    • 데이터가 삭제되어 히스토리 스토어에서도 데이터를 찾지 못할 경우 WT_NOTFOUND 에러가 반환
  • minSnapshotHistoryWindowInSeconds 를 통해 히스토리 스토어의 보관 주기를 설정할 수 있음 (default: 300 sec)
  • 체크포인트 사이에 예기치 못한 종료가 발생할 경우, 히스토리 스토어를 활용해 최신 안정적인 체크포인트 시점으로 일관성 있는 복구가 가능함
 

Journal Directory

  • Write-ahead log(WAL)에 해당하는 저널 로그가 저장되는 폴더
  • 저널로그의 주요 목적: 서버가 비정상적으로 종료되었을 때에도 데이터의 Durability를 보장하기 위해 디스크에 먼저 기록해두는 것
  • WiredTiger는 저널 로그 파일을 재사용하지 않고 새로운 파일을 계속 생성하는 방식을 사용
    • 새로운 파일로의 빠른 전환을 위해 WiredTigerPreplog.* 파일을 미리 생성해 두고, 실제 저널로그가 저장되는 WiredTigerLog.00000N 파일을 모두 사용했을 때 WiredTigerPreplog.* 파일의 이름을 다음 번호로 변경한 후 사용
  • 저널 로그의 내용은 체크포인트 수행 시 데이터 파일에 동기화되므로 체크포인트 시점 이전의 저널로그는 더 이상 필요하지 않아 삭제됨
 
  • optype은 해당 항목의 연산 유형
    • 데이터 insert, update에는 row_put
      • update()의 경우, MongoDB에서 저널링은 local.oplog.rs 컬렉션에만 활성화(log=(enabled=true))되어 있고, OpLog(Operations Log)는 복제를 목적으로 프라이머리의 모든 변경사항을 기록하여 데이터 삽입만 발생
    • 데이터 modify에는 row_modify
  • standalone 배포 형태에서는 복제를 위한 oplog가 없기 때문에 모든 컬렉션과 인덱스 파일에 대한 저널링이 필요
    • standalone 구성으로는 Mongodb의 강력한 장점을 살리지 못하기 때문에 이 배포 형태는 테스트로만 사용하기를 권장!
 
+) MongoDB Standalone
  • 단일 서버(노드)에서 몽고디비 인스턴스를 운영하는 가장 기본적인 배포 형태
  • 하나의 mongod 프로세스가 모든 데이터의 읽기와 쓰기를 담당, 복제 replica set 이나 샤딩 sharding cluster 과 같은 분산 기능이 없는 구조
    • 항목
      Standalone
      Replica Set
      서버 수
      1개
      2개 이상
      고가용성
      미지원
      지원(장애 시 자동 전환)
      데이터 복제
      없음
      Primary → Secondary로 복제
      트랜잭션 지원
      제한적(싱글 도큐먼트만)
      멀티 도큐먼트 트랜잭션 지원
      운영 환경 적합
      비권장(테스트/학습용 권장)
      권장(프로덕션 표준)
 

컬렉션 파일(collection-*)과 인덱스 파일(index-*)

  • 실제 데이터가 저장되는 컬렉션 파일과 인덱스 파일
  • directoryPerDB = True 옵션과 directoryForIndexes = False 옵션을 사용할 경우, 데이터베이스 별로 동일한 디렉토리에 데이터 파일과 인덱스 파일이 함께 위치하게 된다.
    • +) 이렇게 되면, DB 단위 파일 관리(백업, 이동, 삭제)가 간편하고 db별 용량을 모니터링 하거나 디스크 공간을 분리해 관리할 때 유리
 
  • MongoDB에서는 논리적으로 컬렉션 데이터와 인덱스가 구분되어 사용 <> WiredTiger는 기본적으로 모든 데이터를 key-value store 형태로 관리
    • WiredTiger 기준에서는 컬렉션 파일, 인덱스 파일 구분 없이 모두 B+Tree 구조로 관리되며 동일한 방식으로 처리
    • notion image
      +) MongoDB에서 컬렉션(데이터)과 인덱스는 사용자에게 다른 역할로 보이지만 WiredTiger 스토리지 엔진 내부에서는 모두 key-value 형태로 동일한 B+Tree 방식으로 관리된다.
      • 컬렉션 데이터 ⇒ _id를 키로 하는 B+tree.
        • 키(key): 문서의 _id (기본 키) 값(value): 문서 전체 데이터 (BSON)
      • 인덱스 ⇒ 인덱스 필드 값을 키로 하는 B+tree
        • 키(key): 인덱싱된 필드 값 (예: email 필드 값) 값(value): 해당 문서의 _id (문서 위치 포인터)
           
      • 사용자가 email 필드로 검색할 때:
          1. email 인덱스 B+tree에서 해당 이메일 값을 키로 검색 → _id 획득
          1. _id를 키로 컬렉션 B+tree에서 문서 데이터 조회
 
  • 인덱스 파일의 값은 BSON 형식의 대안인 KeyString 형식의 직렬화된 값을 사용
    • BSON 형식은 중첩 구조 등 유연한 데이터 타입을 표현하기 좋지만 비교 연산은 상대적으로 복잡함
    • 인덱스의 주된 목적은 빠른 조회 → MongoDB에서는 이진 비교(Binary Comparable)가 가능한 KeyString 형식을 사용하여 성능을 최적화
 
+) KeyString
MongoDB 내부에서 인덱스 값을 저장하고 정렬하기 위해 사용하는 특별한 이진 형식
항목
BSON
KeyString
장점
유연한 구조 (중첩 문서, 배열 등)
빠른 비교 (정렬 순서가 명확)
단점
비교가 느리고 복잡
구조가 단순 (인덱스 전용)
용도
일반 데이터 저장용
인덱스용 (빠른 조회와 정렬)
타입 정보와 값이 함께 직렬화되어 값 비교 시 그냥 바이트 단위로 정렬할 경우 BSON의 정렬 순서와 완전히 일치 → 정렬, 범위 검색에 유리
  • 다음과 같은 형태로 저장됨
    • [타입 바이트][값 바이트][구분자 바이트]...
    • EX) { age: 30, name: "Alice" } 값은 [Integer 타입][30][String 타입]["Alice"] 이렇게 변환됨
 
  • 인덱스의 경우, 이미 필드 순서가 정의되어 있기 때문에 필드명을 따로 저장할 필요는 없음. 몽고디비는 Schemaless를 지원하기 때문에 동등한 필드에도 여러 데이터 타입을 사용할 수 있어 이를 표현하기 위한 값을 사용
    • BSONObj 타입과 매핑 정보는 Ctype(Canonical types namespace)에 정의
    •  
  • B+tree 구조를 사용하는 _id 인덱스의 leaf 페이지에는 실제 도큐먼트가 저장되는 것이 아닌, 아래 그림과 같이 키(_id), 값(RecordId) 쌍으로 구성된 데이터가 저장되어있음.
    • notion image
    • 장) 기본 키를 클러스터드 인덱스로 구성하면 기본 키에 대한 조회 시 실제 데이터가 포함된 페이지로 직접 연결되기 때문에 위 그림과 같은 구조에 비해 Disk I/O를 절약하며 쿼리 수행 및 삽입 성능에 이점
    • 단) 이런 경우 파일 내의 도큐먼트를 항상 기본 키 값을 기준으로 정렬된 형태로 유지해야 하기 때문에, 기본 키 선택과 사용에 제약이 생겨 유연성에서는 다소 불리
 
+) Clustered Collection
  • MongoDB에서 도입된 컬렉션 유형으로, 컬렉션 내의 문서들이 특정 필드(주로 _id)의 값에 따라 물리적으로 정렬된 상태로 저장되는 컬렉션
    • 문서가 저장되는 순서가 해당 필드의 인덱스 순서와 동일
    • 이 필드를 Clustered Index라고 함
 
  • 범위 쿼리, 시간순 데이터 등에 매우 효율적
    • 데이터가 정렬되어 저장되므로 특정 구간의 데이터를 빠르게 읽고 쓸 수 있음
  • 인덱스와 데이터가 통합되어 읽기/쓰기 성능이 향상됨
    • 인덱스와 데이터가 통합되어 별도의 인덱스 탐색 없이 바로 문서를 찾거나 삽입이 가능해 읽기와 쓰기 모두 성능 개선
    • 별도의 hidden index가 필요 없어 저장 공간이 절약
<> 이미 생성된 컬렉션에는 나중에 clustered index를 추가할 수 없음
<> 여러 필드에 대해 동시에 만들 수 없음
<> Capped Collection과 호환되지 않음
 
+) 클러스터형 컬렉션과 비클러스터형 컬렉션의 성능 차이
클러스터형 컬렉션은 인덱스와 데이터가 통합되어 저장되어, 읽기·쓰기·범위 쿼리·저장 공간 측면에서 비클러스터형 컬렉션보다 훨씬 효율적
→ 클러스터 인덱스 필드 기준으로 자주 접근하거나 범위 쿼리가 많은 워크로드에서 큰 성능 차이 발생
 
항목
클러스터형 컬렉션
비클러스터형 컬렉션
삽입/수정/삭제
한 번의 작업(쓰기/읽기)로 처리되어 빠름
인덱스와 데이터 각각 작업, 오버헤드 있음
단일 키 조회
인덱스 탐색과 데이터 조회가 한 번에 끝남
인덱스 탐색 후 별도 데이터 조회 필요
범위 쿼리
클러스터 인덱스 필드 기준 범위 쿼리 매우 빠름
인덱스와 데이터 파일을 모두 순회해야 함
저장 공간
인덱스와 데이터가 통합되어 공간 효율적
인덱스와 데이터가 분리되어 공간 더 필요
TTL 인덱스
클러스터 인덱스에 TTL 적용 가능, 삭제 효율적
별도의 TTL 인덱스 필요, 삭제 성능 떨어짐
[클러스터형 컬렉션]
  • 문서가 클러스터 인덱스(주로 _id) 필드 값에 따라 물리적으로 정렬되어 저장
  • 인덱스와 데이터가 통합되어 한 번의 읽기/쓰기 작업으로 처리
[비클러스터형 컬렉션]
  • 문서와 인덱스가 분리되어 저장
  • _id 인덱스도 별도의 B-트리로 관리되며, 데이터 접근 시 인덱스와 데이터 파일을 각각 읽거나 써야 함
 
[언제 클러스터형 컬렉션이 유리한가?]
  • 범위 쿼리가 많거나, 데이터가 자연스럽게 정렬되는 경우 (예: 로그, 타임시리즈, 순차적 ID)
  • 대량의 삽입/삭제가 빈번한 경우
  • TTL(만료) 정책이 필요한 경우
 
[한계점 및 주의사항]
  • 클러스터 인덱스는 컬렉션당 하나만 지정 가능
  • 클러스터 인덱스 필드가 너무 크거나 복잡하면 오히려 성능 저하 및 저장 공간 증가가 발생
 
+) Eviction
MongoDB에서 WiredTiger 스토리지 엔진에서 메모리(캐시) 공간을 효율적으로 관리하기 위한 프로세스
Eviction 과정
  • WiredTiger는 디스크에 저장된 데이터를 메모리 캐시에 불러와 작업
  • 메모리 용량이 한정되어 있기 때문에 더 이상 새로운 데이터를 메모리에 올릴 수 없을 때, 사용 빈도가 낮은 데이터를 메모리에서 내보내는 작업이 필요
 
작동 원리
  • Eviction 시작 조건
    • 캐시 사용량이 설정된 임계치(예: 80%)를 넘어서면, Eviction이 자동으로 시작
    • Dirty data(수정된 데이터)가 너무 많아질 때도 Eviction이 발생
    • Eviction은 백그라운드 워커 스레드가 주로 담당하지만, 캐시가 너무 가득 차면 애플리케이션 스레드(사용자 요청 처리 스레드)도 Eviction 작업을 수행
  • Eviction의 동작
      1. 가장 덜 사용된(Least Recently Used) 페이지를 우선적으로 Eviction 대상으로 선정
      1. Clean page(수정되지 않은 데이터)는 바로 캐시에서 제거
      1. Dirty page(수정된 데이터)는 디스크에 기록(동기화)한 뒤 캐시에서 제거
      1. 이 과정에서 페이지가 분할되거나, 히스토리 스토어로 이전 버전이 이동하는 등 내부 처리 발생
  • Eviction의 목적
    • 메모리 캐시가 과도하게 커지는 것을 방지
    • 새로운 데이터 요청이 들어올 때, 빠르게 메모리 공간을 확보
    • 전체 시스템의 성능과 안정성 유지
 
Eviction 관련 주요 설정
  • eviction_target: 캐시 사용량을 이 수준(%) 이하로 유지하려고 시도함 (예: 80%)
  • eviction_trigger: 이 수준(%)을 넘으면 애플리케이션 스레드까지 Eviction에 참여 (예: 95%)
  • eviction_dirty_target/eviction_dirty_trigger: Dirty data(수정된 데이터) 비율에 대한 별도 임계치
Series : Tech Blog InQuery
  • 1.
    Redis 캐시로 몰려드는 트래픽을 견디다 - 토니모리 공식몰 성능 개선기
  • 2.
    PKCE:OAuth를 더욱더 안전하게 만드는 방법
  • 3.
    AWS DataZone에서 OpenLineage 기반의 Airflow 데이터 계보 그리기
  • 4.
    Chaos Toolkit 을 이용한 카오스 엔지니어링(Chaos Engineering)
  • 5.
    성능 향상을 위한 SQL 작성법
  • 6.
    MongoDB WiredTiger의 파일 구조
Related Posts
🤔 Database
[SQL] 개발자가 반드시 알아야 할 쿼리 튜닝의 핵심
Series: 26년 학습

[SQL] 개발자가 반드시 알아야 할 쿼리 튜닝의 핵심

Jan 9, 2026

실무에서 바로 사용하는 SQL 튜닝 가이드. MySQL 아키텍처부터 실행 계획(Explain) 분석 방법, 드라이빙 테이블 선정 기준, 그리고 악성 쿼리(JOIN, UNION) 최적화 사례까지 성능 개선을 위한 핵심 기법을 정리했습니다. A practical guide to SQL tuning for developers. Covers MySQL architecture, execution plan analysis, driving table selection, and real-world optimization examples for JOINs and aggregations to boost query performance.

SQL
MySQL
Query Optimization
Database
🤔 Database
Series: Tech Blog InQuery

성능 향상을 위한 SQL 작성법

May 5, 2025

인덱스 구조와 스캔 원리를 기반으로 쿼리 성능을 극대화하는 실무적인 SQL 작성 가이드를 제시합니다. 커버링 인덱스 활용, 정렬 연산 대체, LIMIT 최적화 등 데이터베이스 부하를 줄이는 구체적인 튜닝 기법을 확인해 보세요.

SQL
NoSQL Performance Optimization
🔎 Practice
Series: Tech Blog InQuery

Chaos Toolkit 을 이용한 카오스 엔지니어링(Chaos Engineering)

May 3, 2025

Chaos Toolkit을 활용하여 시스템의 신뢰성을 검증하는 카오스 엔지니어링의 핵심 개념과 실습 과정을 다룹니다. 실험 설계, 실행, 결과 분석 및 확장 방법까지 단계별 가이드를 통해 안정적인 서비스 운영을 위한 기술적 통찰을 제공합니다.

Chaos Engineering
⚙️ Data Engineering
Series: Tech Blog InQuery

AWS DataZone에서 OpenLineage 기반의 Airflow 데이터 계보 그리기

May 2, 2025

AWS DataZone과 OpenLineage를 연동하여 Airflow 기반의 데이터 계보(Lineage)를 시각화하는 아키텍처와 구축 방법을 다룹니다. 이를 통해 복잡한 데이터 파이프라인의 흐름을 투명하게 관리하고 추적성을 확보하는 기술적 노하우를 확인해 보세요.

Airflow
Data Lineage Tracking