MongoDB WiredTiger의 파일 구조
MongoDB WiredTiger의 파일 구조 - tech.kakao.com
들어가며 안녕하세요, 카카오 분산데이터베이스 조직에서 MongoDB를 운영하고 있...
- 이전에 몽고디비를 사용한 적 있고
- 앞으로 몽고디비를 잘 활용하는 서비스에 기여해보고 싶다는 생각을 하고 있고
- 그리고 디비 구조에 대해서 하나 깊게 알아두면 다른 디비 볼 때에도 도움이 될 수 있으리라 생각해서

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

- 스토리지 엔진은 메모리와 디스크에 모두 데이터가 저장되는 방식을 관리하는 등 몽고디비에서 핵심적인 역할을 수행
- 플러그형 스토리지 엔진 아키텍처를 지원해서 다양한 스토리지 엔진을 지원했지만 현재는 WiredTiger 스토리지 엔진을 기본 스토리지 엔진으로 상요하고 있음
- WiredTiger
- 트랜잭션 지원
- Lock Free 알고리즘
- 데이터 압축
- key-value store
- 메모리는 매우 빠르지만 용량이 한정되어 있고, 휘발성입니다.
- 디스크는 느리지만 대용량 데이터를 영구적으로 저장할 수 있습니다.
- 따라서, 자주 접근하는 데이터나 최근 변경된 데이터는 메모리에 두고, 전체 데이터의 일관성과 내구성은 디스크에 저장함으로써 성능과 안정성을 모두 확보
분석 도구
- BSON; Binary JSON
- 몽고디비에서 데이터를 저장할 때 사용하는 표준 포맷
- 바이너리 형태로 이뤄져있음
- 이를 읽기 위해
bsondump를 통해 JSON 형식으로 변환
wt 도 있음. 이는 WiredTIger 소스코드에서 직접 빌드해야 함WiredTiger에서 관리하는 파일 구조
WiredTiger
- WiredTiger 파일은 텍스트 파일
- 현재 실행 중인 WiredTiger 스토리지 엔진의 버전 정보를 저장하고 있음
- MongoDB와 WiredTiger 버전 관계
- WiredTiger의 변경 사항은 MongoDB에도 큰 변화를 가져옴
WiredTiger.lock
- 다른 MongoDB 서버 인스턴스가 데이터 파일들에 동시에 접근하는 것을 방지
- 서버가 정상적으로 종료되었는지 여부를 판단하는 역할
- 서버가 시작될 때, WiredTiger.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)
- log=(enable=false) vs. log=(enabled=true)
- log 설정은 WiredTiger의 write-ahead log 기능인 journaling의 활성화 여부를 결정
- <> 사용자가 생성한 컬렉션에 대한 데이터 파일과 인덱스 파일에는 log 설정이 비활성화됨 ← 성능을 위해서임 (MongoDB의 write durability는 대부분 oplog와 checkpointing에 의존)
- 몽고디비는 oplog에 db의 모든 변경 사항을 저장하므로 oplog에 대한 journaling만으로도 복구할 수 있음
- oplog는 가능하면 원본 데이터가 아니라 변경분만 저장하여 효율적이라는 장점도 있음
- log 옵션이 활성화되지 않은 데이터 및 인덱스 파일에 대한 쓰기는 기본적으로 메모리에 유지되고, eviction 혹은 checkpoint를 통해서만 디스크로 쓰이며 checkpoint-durability 만을 보장
성능을 위해서는 모든 파일에 대해 저널링을 하는 것보다, oplog에 대한 저널링만 선택하는 것이 합리적인 선택이라 생각
- id=90, id=92, id=18
- 각 항목의 id값은 파일의 고유한 식별자
- 다른 파일에서 참조할 때 일반적으로 파일의 이름이 아닌 여기에 설정된 file id를 사용
- 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를 필터링할 수 있는 기능은 없음
- 청크 마이그레이션에 실패했거나 비정상적인 종료로 인해 정리되지 못하고 이전 샤드에 그대로 남아있는 도큐먼트
estimatedDocumentCount()에는 Orphaned Document가 포함된 결과를 반환- 필터링한 정확한 결과를 얻기 위해서는 mongos에서 SHARDING_FILTER 스테이지를 수행할 수 있는
countDocuments()명령어를 사용
estimatedDocumentCount()와 countDocuments() 의 차이구분 | estimatedDocumentCount() | countDocuments() |
정확성 | 대략적인(추정) 값 | 정확한 값 |
필터 지원 | 지원하지 않음 (전체 문서만) | 쿼리 조건 지원 (필터링 가능) |
동작 방식 | 컬렉션의 메타데이터(인덱스 통계 등)를 이용해 빠르게 추정 | 실제로 컬렉션을 스캔하여 조건에 맞는 문서 수 계산 |
성능 | 매우 빠름 (대용량 컬렉션도 빠름) | 느릴 수 있음 (조건 복잡/컬렉션 대용량일수록 느려짐) |
사용 예 | 대시보드, 빠른 개수 확인 등 대략적인 수치가 필요한 경우 | 조건에 맞는 정확한 문서 수가 필요한 경우 |
_mdb_catalog.wt
- MongoDB 사용자에게 표시되는 컬렉션과 인덱스에 대한 메타데이터가 저장
<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)의 데이터를 관리
- MVCC; Multi Version Concurrency Control 를 활용하여 여러 버전의 데이터 변경사항을 메모리에 유지
- 변경사항은 Update-Chain이라고 불리는 Singly linked list에 저장됨
- 새로운 변경사항이 발생할 경우, 업데이트 체인의 맨 앞에 추가되어, 가장 최근 업데이트가 맨 앞에 위치하게 됨
- 최신 버전 우선 접근
- MVCC(Multi-Version Concurrency Control) 지원
- 일관성 및 동시성 관리
맨 뒤에 추가한다면, 최신 데이터를 찾기 위해 체인을 끝까지 순회해야 하므로 비효율적

- 메모리 상의 데이터는 eviction 혹은 checkpoint 과정을 통해 디스크에 작성됨 → 메모리에 있는 여러 버전 중 하나의 버전만 디스크에 쓰임
- 어떤 버전을 작성할 것인지 선택하는 과정: WiredTiger의 Reconciliation

- 디스크에 쓰일 이미지가 선택되면 이전 변경사항들은 메모리 상에 제거되어야 함
- 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의 강력한 장점을 살리지 못하기 때문에 이 배포 형태는 테스트로만 사용하기를 권장!
- 단일 서버(노드)에서 몽고디비 인스턴스를 운영하는 가장 기본적인 배포 형태
- 하나의 mongod 프로세스가 모든 데이터의 읽기와 쓰기를 담당, 복제 replica set 이나 샤딩 sharding cluster 과 같은 분산 기능이 없는 구조
항목 | Standalone | Replica Set |
서버 수 | 1개 | 2개 이상 |
고가용성 | 미지원 | 지원(장애 시 자동 전환) |
데이터 복제 | 없음 | Primary → Secondary로 복제 |
트랜잭션 지원 | 제한적(싱글 도큐먼트만) | 멀티 도큐먼트 트랜잭션 지원 |
운영 환경 적합 | 비권장(테스트/학습용 권장) | 권장(프로덕션 표준) |
컬렉션 파일(collection-*)과 인덱스 파일(index-*)
- 실제 데이터가 저장되는 컬렉션 파일과 인덱스 파일
- directoryPerDB = True 옵션과 directoryForIndexes = False 옵션을 사용할 경우, 데이터베이스 별로 동일한 디렉토리에 데이터 파일과 인덱스 파일이 함께 위치하게 된다.
- MongoDB에서는 논리적으로 컬렉션 데이터와 인덱스가 구분되어 사용 <> WiredTiger는 기본적으로 모든 데이터를 key-value store 형태로 관리
- WiredTiger 기준에서는 컬렉션 파일, 인덱스 파일 구분 없이 모두 B+Tree 구조로 관리되며 동일한 방식으로 처리
- 컬렉션 데이터 ⇒
_id를 키로 하는 B+tree. - 인덱스 ⇒ 인덱스 필드 값을 키로 하는 B+tree
- 사용자가
email필드로 검색할 때: email인덱스 B+tree에서 해당 이메일 값을 키로 검색 →_id획득_id를 키로 컬렉션 B+tree에서 문서 데이터 조회

_id (기본 키)
값(value): 문서 전체 데이터 (BSON)email 필드 값)
값(value): 해당 문서의 _id (문서 위치 포인터)- 인덱스 파일의 값은 BSON 형식의 대안인 KeyString 형식의 직렬화된 값을 사용
- BSON 형식은 중첩 구조 등 유연한 데이터 타입을 표현하기 좋지만 비교 연산은 상대적으로 복잡함
- 인덱스의 주된 목적은 빠른 조회 → MongoDB에서는 이진 비교(Binary Comparable)가 가능한 KeyString 형식을 사용하여 성능을 최적화
항목 | BSON | KeyString |
장점 | 유연한 구조 (중첩 문서, 배열 등) | 빠른 비교 (정렬 순서가 명확) |
단점 | 비교가 느리고 복잡 | 구조가 단순 (인덱스 전용) |
용도 | 일반 데이터 저장용 | 인덱스용 (빠른 조회와 정렬) |
- 다음과 같은 형태로 저장됨
[타입 바이트][값 바이트][구분자 바이트]...
{ age: 30, name: "Alice" } 값은 [Integer 타입][30][String 타입]["Alice"] 이렇게 변환됨- 인덱스의 경우, 이미 필드 순서가 정의되어 있기 때문에 필드명을 따로 저장할 필요는 없음. 몽고디비는 Schemaless를 지원하기 때문에 동등한 필드에도 여러 데이터 타입을 사용할 수 있어 이를 표현하기 위한 값을 사용
- BSONObj 타입과 매핑 정보는 Ctype(Canonical types namespace)에 정의
- B+tree 구조를 사용하는 _id 인덱스의 leaf 페이지에는 실제 도큐먼트가 저장되는 것이 아닌, 아래 그림과 같이 키(_id), 값(RecordId) 쌍으로 구성된 데이터가 저장되어있음.
- 장) 기본 키를 클러스터드 인덱스로 구성하면 기본 키에 대한 조회 시 실제 데이터가 포함된 페이지로 직접 연결되기 때문에 위 그림과 같은 구조에 비해 Disk I/O를 절약하며 쿼리 수행 및 삽입 성능에 이점
- 단) 이런 경우 파일 내의 도큐먼트를 항상 기본 키 값을 기준으로 정렬된 형태로 유지해야 하기 때문에, 기본 키 선택과 사용에 제약이 생겨 유연성에서는 다소 불리

- MongoDB에서 도입된 컬렉션 유형으로, 컬렉션 내의 문서들이 특정 필드(주로
_id)의 값에 따라 물리적으로 정렬된 상태로 저장되는 컬렉션 - 문서가 저장되는 순서가 해당 필드의 인덱스 순서와 동일
- 이 필드를 Clustered Index라고 함
- 범위 쿼리, 시간순 데이터 등에 매우 효율적
- 데이터가 정렬되어 저장되므로 특정 구간의 데이터를 빠르게 읽고 쓸 수 있음
- 인덱스와 데이터가 통합되어 읽기/쓰기 성능이 향상됨
- 인덱스와 데이터가 통합되어 별도의 인덱스 탐색 없이 바로 문서를 찾거나 삽입이 가능해 읽기와 쓰기 모두 성능 개선
- 별도의 hidden index가 필요 없어 저장 공간이 절약
항목 | 클러스터형 컬렉션 | 비클러스터형 컬렉션 |
삽입/수정/삭제 | 한 번의 작업(쓰기/읽기)로 처리되어 빠름 | 인덱스와 데이터 각각 작업, 오버헤드 있음 |
단일 키 조회 | 인덱스 탐색과 데이터 조회가 한 번에 끝남 | 인덱스 탐색 후 별도 데이터 조회 필요 |
범위 쿼리 | 클러스터 인덱스 필드 기준 범위 쿼리 매우 빠름 | 인덱스와 데이터 파일을 모두 순회해야 함 |
저장 공간 | 인덱스와 데이터가 통합되어 공간 효율적 | 인덱스와 데이터가 분리되어 공간 더 필요 |
TTL 인덱스 | 클러스터 인덱스에 TTL 적용 가능, 삭제 효율적 | 별도의 TTL 인덱스 필요, 삭제 성능 떨어짐 |
- 문서가 클러스터 인덱스(주로
_id) 필드 값에 따라 물리적으로 정렬되어 저장
- 인덱스와 데이터가 통합되어 한 번의 읽기/쓰기 작업으로 처리
- 문서와 인덱스가 분리되어 저장
_id인덱스도 별도의 B-트리로 관리되며, 데이터 접근 시 인덱스와 데이터 파일을 각각 읽거나 써야 함
- 범위 쿼리가 많거나, 데이터가 자연스럽게 정렬되는 경우 (예: 로그, 타임시리즈, 순차적 ID)
- 대량의 삽입/삭제가 빈번한 경우
- TTL(만료) 정책이 필요한 경우
- 클러스터 인덱스는 컬렉션당 하나만 지정 가능
- 클러스터 인덱스 필드가 너무 크거나 복잡하면 오히려 성능 저하 및 저장 공간 증가가 발생
Eviction 과정
- WiredTiger는 디스크에 저장된 데이터를 메모리 캐시에 불러와 작업
- 메모리 용량이 한정되어 있기 때문에 더 이상 새로운 데이터를 메모리에 올릴 수 없을 때, 사용 빈도가 낮은 데이터를 메모리에서 내보내는 작업이 필요
- Eviction 시작 조건
- 캐시 사용량이 설정된 임계치(예: 80%)를 넘어서면, Eviction이 자동으로 시작
- Dirty data(수정된 데이터)가 너무 많아질 때도 Eviction이 발생
- Eviction은 백그라운드 워커 스레드가 주로 담당하지만, 캐시가 너무 가득 차면 애플리케이션 스레드(사용자 요청 처리 스레드)도 Eviction 작업을 수행
- Eviction의 동작
- 가장 덜 사용된(Least Recently Used) 페이지를 우선적으로 Eviction 대상으로 선정
- Clean page(수정되지 않은 데이터)는 바로 캐시에서 제거
- Dirty page(수정된 데이터)는 디스크에 기록(동기화)한 뒤 캐시에서 제거
- 이 과정에서 페이지가 분할되거나, 히스토리 스토어로 이전 버전이 이동하는 등 내부 처리 발생
- Eviction의 목적
- 메모리 캐시가 과도하게 커지는 것을 방지
- 새로운 데이터 요청이 들어올 때, 빠르게 메모리 공간을 확보
- 전체 시스템의 성능과 안정성 유지
eviction_target: 캐시 사용량을 이 수준(%) 이하로 유지하려고 시도함 (예: 80%)
eviction_trigger: 이 수준(%)을 넘으면 애플리케이션 스레드까지 Eviction에 참여 (예: 95%)
eviction_dirty_target/eviction_dirty_trigger: Dirty data(수정된 데이터) 비율에 대한 별도 임계치
- 1.Redis 캐시로 몰려드는 트래픽을 견디다 - 토니모리 공식몰 성능 개선기
- 2.PKCE:OAuth를 더욱더 안전하게 만드는 방법
- 3.AWS DataZone에서 OpenLineage 기반의 Airflow 데이터 계보 그리기
- 4.Chaos Toolkit 을 이용한 카오스 엔지니어링(Chaos Engineering)
- 5.성능 향상을 위한 SQL 작성법
- 6.MongoDB WiredTiger의 파일 구조
![[SQL] 개발자가 반드시 알아야 할 쿼리 튜닝의 핵심](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fattachment%253Aa9476e05-643e-4db3-8280-8b83d382ee31%253Aimage.png%3Ftable%3Dblock%26id%3D2e3f7343-f364-805a-984b-d6ede33e67cf%26cache%3Dv2&w=3840&q=75)
[SQL] 개발자가 반드시 알아야 할 쿼리 튜닝의 핵심
실무에서 바로 사용하는 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 작성법
인덱스 구조와 스캔 원리를 기반으로 쿼리 성능을 극대화하는 실무적인 SQL 작성 가이드를 제시합니다. 커버링 인덱스 활용, 정렬 연산 대체, LIMIT 최적화 등 데이터베이스 부하를 줄이는 구체적인 튜닝 기법을 확인해 보세요.
Chaos Toolkit 을 이용한 카오스 엔지니어링(Chaos Engineering)
Chaos Toolkit을 활용하여 시스템의 신뢰성을 검증하는 카오스 엔지니어링의 핵심 개념과 실습 과정을 다룹니다. 실험 설계, 실행, 결과 분석 및 확장 방법까지 단계별 가이드를 통해 안정적인 서비스 운영을 위한 기술적 통찰을 제공합니다.
AWS DataZone에서 OpenLineage 기반의 Airflow 데이터 계보 그리기
AWS DataZone과 OpenLineage를 연동하여 Airflow 기반의 데이터 계보(Lineage)를 시각화하는 아키텍처와 구축 방법을 다룹니다. 이를 통해 복잡한 데이터 파이프라인의 흐름을 투명하게 관리하고 추적성을 확보하는 기술적 노하우를 확인해 보세요.