[MongoDB] 잠금, 트랜잭션

2020. 12. 19. 03:07Database/MongoDB

1. 잠금

1) 잠금

MongoDB 서버에서도 멀티 쓰레드의 동시 처리 중에 발생할 수 있는 쓰레드 간의 충돌 문제를 막기 위해서 데이터베이스와 컬렉션 그리고 Document들의 잠금을 사용한다. MongoDB에서도 여러 계층의 데이터베이스 오브젝트들에 대한 동시 처리를 위해서 인텐션 락과 다중 레벨의 잠금을 활용하고 있다.

 

잠금은 크게 2가지로 나누어 볼 수 있다. 명시적 잠금은 글로벌 잠금뿐이며, 모든 잠금은 묵시적 잠금이다. 데이터베이스와 컬렉션에 대한 잠금(오브젝트 잠금)은 모두 묵시적인 잠금이며, 이는 쿼리가 실행될 때 자동으로 획득됐다가 자동으로 해제되는 잠금이다.

2) 글로벌 잠금

글로벌 잠금은 쿼리나 데이터 변경 명령이 실행되면 묵시적으로 MongoDB 서버에서 잠금을 획득했다가 필요 없는 시점에 자동으로 해제된다. 글로벌 잠금은 MongoDB 서버 인스턴스에 단 하나만 있는 잠금이기에 인스턴스 잠금이라고도 한다.

 

fsync 옵션을 1로 설정하면 MongoDB 서버는 디스크에 기록되지 못한 데이터를 모두 디스크로 기록한다. 그리고 lock 옵션이 true가 되면 MongoDB 서버의 글로벌 잠금을 획득한다. fsyncLock() 메서드는 내부적으로 쓰기 잠금이 아닌 읽기 잠금에 해당된다. 그래서 MongoDB 서버의 데이터가 변경되는 것을 막는 용도의 잠금이다.

 

fsynckLock()은 모든 커넥션의 데이터 저장이나 변경을 막기 때문에 커넥션에서도 데이터 저장이나 변경을 실행할 수 없다. 데이터 변경과 데이터 읽기가 동시에 실행되는 커넥션의 경우에는 데이터 변경 명령이 블락킹되므로 다른 읽기 쿼리도 실행하지 못하게 된다. 예를 들어 find - update(_id: 123) - find(_id:123) 명령을 수행했을 때, 이전까지의 데이터 읽기는 가능하지만, 데이터 변경 쿼리가 응답을 받지 못하고 블락킹 되기 때문에 그 이후에 있는 데이터 읽기나 변경이 모두 멈추게 된다. 그렇기 때문에 fsyncLock() 메서드 사용은 조심해야한다.

db.fsyncLock( { fsync: 1, lock: true } )
{
  "info": "now locked against writes, use db.fsyncUnlock() to unlock",
  "lockCount": NumberLong(1),
  "seeAlso": "http://dochub.mongodb.org/core/fsynccommand",
  "ok": 1
}

글로벌 잠금이 걸리면 db.currentOp() 메서드로 글로벌 잠금의 상태를 확인할 수 있다.

db.currentOp()
{
  "inprog": [...],
  "fsyncLock": true,
  "info": "use db.fsyncUnlock() to terminate the fsync write/snapshot lock",
  "ok": 1
}

fsyncUnlock() 메서드로 잠금을 해지할 수 있다.

db.fsyncUnlock()

3) 그 외 잠금 및 개념

그 외의 잠금은 다음과 같다.

  • MMAPv1 스토리지 엔진의 잠금
  • WiredTiger 스토리지 엔진의 잠금

 

※ MMAPv1은 기본 스토리지 엔진으로 이전 버전에서 사용된 엔진이다. 컬렉션 수준의 잠금이 제공된다.

※ WiredTiger은 새로운 스토리지 엔진으로 문서 수준 잠금 및 압축 기능이 제공된다. (64 비트 버전에서만 사용 가능)

 

잠금에서 필요한 개념은 다음과 같다.

  • 잠금 Yield

 

잠금 Yield는 쿼리를 실행하는 도중에 잠깐 귀었다가 쿼리의 실행을 재개하는 것을 Yield(양보)라고 한다. MongoDB 서버의 Yield는 단순히 쿼리의 처리를 멈추고 잠깐 쉬는것이 아닌, 처리 중인 쿼리를 위해서 획득했던 잠금까지 모두 해제하고 지정된 시간동안 쉬게 된다.

 

 

 

2. 트랜잭션

1) WiredTiger 트랜잭션

MongoDB 서버에서 트랜잭션은 주된 목적이 아니다. 따라서 MMAPv1 스토리지 엔진을 사용했을 때에는 트랜잭션의 요소가 없었다. 하지만 WiredTiger 스토리지 엔진이 등장하면서 트랜잭션 처리에 대한 고려가 필요해졌다.

 

WiredTiger 스토리지 엔진이 제공하는 트랜잭션의 ACID는 다음과 같다.

  • 최고 레벨의 격리 수준은 Snapshot (Repeatable-Read)
  • 트랜잭션의 커밋과 체크포인트 2가지 형태로 영속성 보장
  • 커밋되지 않은 변경 데이터는 공유 캐시 크기보다 작아야함

 

기존 RDBMS에서 Read-Uncommitted, Read-Committed, Repeatable-Read, Serializable 4가지의 격리 수준을 제공하지만, WiredTiger 스토리지 엔진에서는 Repeatable-Read와 같은 Snapshot 격리 수준이 최고 수준이다. 커밋과 체크포인트 2가지 형태로 영속성을 보장하기 때문에 로그가 없어도 마지막 체크포인트 시점의 데이터를 복구할 수 있다. 그리고 트랜잭션이 커밋되기 전에는 트랜잭션 로그를 디스크로 기록하지 않는다.

2) 쓰기 충돌

RDBMS 서버에서는 두 세션이 동시에 하나의 레코드를 변경하고자 할 때, 해당 레코드에 대해 잠금을 먼저 획득한 세션이 데이터를 모두 변경하고 잠금을 해제할 때까지 나머지 세션은 기다리게 된다.

 

반면 MongoDB 서버는 하나의 데이터를 동시에 변경하려고 하는 상황을 쓰기 충돌이 일어난다. MongoDB 서버는 변경하고자 하는 Document가 이미 다른 커넥션에 의해 잠금이 걸려있으면 즉시 업데이트 실행을 취소한다. 이때 MongoDB 서버의 각 스토리지 엔진은 WriteConflict Exception을 반환한다. 그러면 업데이트 명령을 실행하던 세션은 WriteConflict Exception을 받고, 같은 업데이트 문장을 재실행한다.

 

이러한 재처리 과정은 MongoDB 서버 프로세스 내부에서만 실행되며, 하나의 Document를 많은 쓰레드가 동시에 변경하려고 하는 경우에는 심각한 문제가 될 수 있다. MongoDB 서버에서는 하나의 Document에 변경이 집중되어 쓰기 충돌과 재처리 과정이 반복 실행되면서 CPU의 사용량이 높아지지만 실제 사용자의 요청을 처리하는 성능은 떨어질 가능성이 높다.

 

MongoDB에서 WriteConflict Exception이 얼마나 발생했는지 확인하는 방법은 다음과 같다.

db.serverStatus()

3) Single Document 트랜잭션

MongoDB는 처음 시작부터 Single Document 트랜잭션만 지원하고 있다. MongoDB 서버에서 Document의 데이터를 변경하면 실제 이 작업들은 잘게 쪼개지고, WiredTiger 스토리지 엔진에는 여러 컬렉션을 변경하는 작업으로 나누어진다.

4) 문장의 트랜잭션 처리

MongoDB 서버가 Document 단위의 트랜잭션만 지원한다. 그렇다면 하나의 INSERT 문장에 여러 Document가 저장되는 배치 INSERT와 한번에 여러 Document를 변경하는 업데이트 문장의 트랜잭션은 하나씩 쪼개져서 실행된다.


[참고] Real MongoDB

728x90

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

[MongoDB] 백업 및 복구  (0) 2020.12.21
[MongoDB] 보안  (1) 2020.12.20
[MongoDB] Index  (0) 2020.12.19
[MongoDB] Aggregation Pipeline  (0) 2020.12.18