본문 바로가기

Database/MongoDB

MongoDB Journaling

다른 여타 데이터베이스들과 동일하게 MongoDB는 데이터의 영속성을 보장하기 위해서 Journaling이라는 기법을 사용한다.
다른 데이터베이스들에서는 WAL(Write Ahead Log)이라고 불리기도 한다. Journaling은 무엇이고 왜 쓰는지에 대해서 알아보자.

Journaling 이란?

MongoDB의 공식 홈페이지에 보면 Journaling에 대해서 다음과 같이 설명하고 있다.

https://www.mongodb.com/docs/manual/core/journaling/
To provide durability in the event of a failure, MongoDB uses write ahead logging to on-disk journal files.

MongoDB 서버가 예기치못하게 종료되는 경우 데이터의 내구성(durability)를 보장하기 위해 디스크에 있는 Journal파일에 WAL( Write Ahead Log )을 기록한다.

MongoDB의 데이터 쓰기

MongoDB는 DML이 들어온 경우 DML의 데이터를 곧바고 디스크에 기록하지 않는다. MongoDB의 스토리지 엔진인 WiredTiger는 Block 단위로 데이터를 읽고 쓰며, 디스크에는 압축된 상태의 데이터가 기록되기 때문에 DML이 들어올 때 마다 데이터를 쓰는 경우 MongoDB의 쓰기는 굉장히 느려질 것이다. 쓰기 성능을 향상시키기 위해서 MongoDB는 DML이 들어온 경우 데이터를 곧바로 디스크에 쓰지 않으며 메모리상에 보관하고 있다가 일정 주기로 실행되는 Checkpoint 시점에 일괄적으로 변경된 데이터들을 기록한다. MongoDB의 기본 Checkpoint 주기는 60초이며, syncdelay 라는 서버 파라미터나 storage.syncPeriodSecs라는 컨피그 파일 옵션으로 설정할 수 있다. 이 파라미터를 60초가 아닌 다른 값으로 조절하는 것은 권장되지 않는다.

체크포인트를 통해서만 데이터를 쓴다면 체크포인트 사이에 MongoDB가 종료되는 경우 데이터가 유실될 수 있다. 60초 주기로 체크포인트가 실행되므로 최대 60초 + Checkpoint에 걸리는 시간 동안의 데이터가 유실될 가능성이 있다. 따라서 MongoDB는 Checkpoint 사이의 데이터 변경분에 대해서만 기록하는 Journaling이라는 기법을 사용한다.

MongoDB는 DML 요청이 들어오면 먼저 데이터의 변경에 대한 내용을 Journal 파일에 기록한다. 체크포인트 사이에 MongoDB가 종료되는 경우, MongoDB를 재기동할 때 Journal 파일을 읽어 이전의 체크포인트 시점으로 부터 반영되지 않은 데이터들을 가져와 반영한다. 체크포인트 시점의 데이터 + Journal 파일에 기록된 데이터를 통해 MongoDB가 쓰기 요청을 받은 데이터들을 안전하게 보관할 수 있다.

Journaling

MongoDB는 공식 문서에 따르면 DML 요청에 대해서 다음 시점에 Journal 파일에 기록한다고 한다.

  • Replica Set을 구성하는 primary, secondary 멤버에 대해서
    • writeConcern이 j:true 인 쓰기 요청이 들어오는 경우
      • secondary 멤버의 경우 oplog 항목들을 batch 형태로 적용한 뒤에 journal 쓰기가 발생된다.
  • 100ms 마다 ( 이 값은 storage.journal.commitIntervalMs 으로 설정할 수 있으나 변경이 권장되지 않는다. )
  • 신규 Journal 파일을 생성하는 경우 ( MongoDB의 Journal 파일 사이즈는 100MB이고, Journal 파일에 기록되는 내용이 100MB를 초과하는 경우 신규 Journal 파일을 생성한다. )

Journal 파일에 데이터를 쓰는 주기는 storage.journal.commitIntervalMs 에 명시된 값으로, 기본값은 100ms로 설정되어있다. 하지만 Replica Set에서는 j:true 인 쓰기 요청이 들어오는 경우 journaling이 수행되도록 되어있어서, 데이터의 유실이 발생하지 않는다.

Journaling 테스트

간단한 테스트를 통해서 이를 확인할 수 있다.
테스트는 MongoDB 6.0.14 버전에서 진행되었고 3개의 멤버로 구성된 Replica Set에서 진행되었다.

테스트를 위해 4개의 터미널을 통시에 열고, 다음과 같이 준비한다.

  • 1번 터미널
mongod [primary] test> db.test.find();
[
  { _id: ObjectId("670663e7e27ac75717cf07a4"), test: 1 },
  { _id: ObjectId("67066461e27ac75717cf07a5"), test: 2 },
  { _id: ObjectId("67066470e27ac75717cf07a6"), test: 3 },
  { _id: ObjectId("670664f0e27ac75717cf07a7"), test: 4 },
  { _id: ObjectId("6706654be27ac75717cf07a8"), test: 5 },
  { _id: ObjectId("6706666ce27ac75717cf07a9"), test: 6 }
]
mongod [primary] test> db.test.insertOne({test: 7},{writeConcern : {w:1, j: true, wtimeout: 1000}}); <-- 이 상태에서 대기한다. 

3개의 mongod 프로세스를 동시에 kill할 수 있도록 다음과 같이 준비한다.

  • 2번 터미널 ~ 4번 터미널
[:/mongodb]$ ps -ef | grep mongo
mongodb  86144  86143  0 19:52 pts/0    00:00:18 mongosh mongodb://-
mongodb      97281      1  1 20:18 ?        00:00:07 /mongod -f /conf/mongodb.conf
mongodb  99911  85988  0 20:25 pts/1    00:00:00 grep --color=auto mongo
[/mongodb]$ sleep 0.05; sudo kill -9 97281 <-- 이 상태에서 대기한다. 

그리고 동시에 엔터를 실행한다.

1번 터미널에서 ACK를 받았음을 확인한다.

mongod [primary] test> db.test.insertOne({test: 7},{writeConcern : {w:1, j: true, wtimeout: 1000}});
{
  acknowledged: true,
  insertedId: ObjectId("6706689fe27ac75717cf07aa")
}

test>

하지만 곧바로 연결이 끊기게 된다.
이후 다시 MongoDB를 재기동하고 확인해보면, 다음과 같이 7번 데이터에 대해서 데이터가 기록이 되어있는 것을 확인할 수 있다.


mongod [primary] test> db.test.find();
[
  { _id: ObjectId("670663e7e27ac75717cf07a4"), test: 1 },
  { _id: ObjectId("67066461e27ac75717cf07a5"), test: 2 },
  { _id: ObjectId("67066470e27ac75717cf07a6"), test: 3 },
  { _id: ObjectId("670664f0e27ac75717cf07a7"), test: 4 },
  { _id: ObjectId("6706654be27ac75717cf07a8"), test: 5 },
  { _id: ObjectId("6706666ce27ac75717cf07a9"), test: 6 },
  { _id: ObjectId("6706689fe27ac75717cf07aa"), test: 7 }
]
mongod [primary] test>

Journaling이 잘 동작하고 있는 것을 확인할 수 있다.

 

하지만 실제로 여러번 테스트를 해봤을 때, 가끔씩 데이터가 확인되지 않는 케이스도 발견되었는데, j:true 옵션을 명시적으로 주지 않는 경우 이런 현상이 더욱 자주 발생했다. 좀 더 자세한 케이스에 대해 확인이 필요해보인다. 

 

또한 주의해야할 점은이렇게 갑작스럽게 죽는 경우 db.test.count()를 출력했을 때 메타데이터의 수가 달라질 수 있다는 것이다. 그래서 정확한 document의 수를 확인하고 싶은 경우 db.test.countDocuments()를 통해 실제 디스크에 존재하는 데이터의 수를 정확하게 확인해야한다. 

 

결론

MongoDB에서는 쓰기 성능을 향상시키면서 데이터의 내구성(durability)을 보장하기 위해서 Checkpoint와 Journaling 과 같은 기법을 사용하고 있다. 이러한 기법은 다른 DBMS 들에서도 자주 사용되는 방식인 것 같다.

'Database > MongoDB' 카테고리의 다른 글

MongoDB Replication  (1) 2024.09.15
mongodb 인스턴스 만들기  (1) 2023.12.07
MongoDB의 Failover  (0) 2023.11.19
MongoDB 로그 관리 : logRotate와 로그파일 권한  (1) 2023.11.16
MongoDB는 1 Petabyte를 저장할 수 있을까?  (0) 2023.08.26