Read & Write Concern 과 Read Preference
MongoDB 는 분산처리를 기본 아키텍처로 선택하고 있기 때문에 단일 노드에서 단일 노드의 격리 수준뿐 아니라 레플리카 셋을 구성하는 멤버들 간의 동기화까지 제어할 수 있어야 한다.
그래서 MongoDB 서버는 데이터의 Durability 수준에 따라 데이터를 변경하거나 조회할 수 있도록 Read, Write Concern 옵션을 제공한다.
클라이언트 프로그램에서 쿼리 단위로 다르게 설정할 수 있다.
Write Concern
트랜잭션의 시작과 종료를 명시적으로 실행할 방법이 없다.
따라서 MongoDB 서버는 도큐먼트를 저장할 때 사용자의 데이터 변경 요청에 응답이 반환되는 시점이 트랜잭션의 커밋으로 간주된다.
이렇게 사용자의 변경 요청에 응답하는 시점을 결정하는 옵션을 WriteConcern 이라 한다.
클라이언트, 데이터베이스 ,컬렉션 레벨의 3가지 방법으로 WriteConcern 을 설정할 수 있다.
단일 노드 동기화 제어
MongoDB 서버 내부적으로 서버 내부적으로 변경된 데이터가 어느정도 디스크에 동기화되었을때 사용자의 변경 요청에 '완료' 를 보낼 것인지 판단하는 기준이다.
단일노드 동기화 제어옵션으로는 아래 4가지가 있는데 이중 FSYNC 옵션은 MMAPv1 스토리지 엔진에서 사용되던 옵션이고 WiredTiger 에서는 거의 사용되지 않고 의미도 없다.
옵션 | 설명 |
UNACKNOWLEDGED | 초창기 버전에 자주 사용되던 옵션으로, 사용자가 데이터를 저장 변경하는 요청을 보내기만 하고 실제 MongoDB 서버의 응답을 기다리지 않는다. 즉, 클라이언트가 서버로 전송된 명령이 정상적으로 처리되었는지 아니면 에러로 중간에 작업이 멈췄는지 관심을 가지지 않는다. 따라서 결과를 확인하기 위해 getLastError 명령을 실행해야 했다. 2.6 버전 이후로는 통신 프로토콜이 개선되어 별도로 getLastError 명령을 실행하지 않고도 데이터 변경 요청의 성공 및 실패 여부를 받는 형태가 되었다. UNACKNOWLEDGED 모드에서는 실제 데이터 변경 요청이 성공, 실패했는지 확인하지 않고 다음 쿼리를 실행하기 때문에 실제 데이터 변경이 적용되지 않았는데 같은 세션에서 그 데이터를 조회하는 쿼리가 실행될 수 있다. ( 이럴 수 있음 ?? ) 그래서 변경한 데이터가 그 이후 실행된 조회 쿼리에서 보이지 않는 문제점이 있었다. 데이터 조회를 거의 사용하지 않거나 로그나 분석용으로는 사용할 만하, 그 외에는 사용하지 말아야 한다. |
ACKNOWLEDGED | 최근 버전의 MongoDB 서버에서 사용하는 WriteConcern 의 기본값이다. 클라이언트가 변경 요청을 전송하면 변경 내용을 메모리상에서만 적용하고 바로 클라이언트로 성공 또는 실패 응답을 반환한다. 일단 데이터 변경요청이 성공하면 적어도 서버의 메모리상에는 적용된 상태이다. 그래서 즉시 변경된 데이터를 조회해도 변경된 내용이 반환된다. 하지만 메모리상에서 변경된 데ㅣㅇ터가 디스크로 동기화 되는 것을 보장하지는 않는다. 그래서 메모리상의 변경 데이터가 디스크로 동기화 되기 전에 서버에 문제가 생기거나 비정상적으로 종료되면 변경된 데이터가 손실될 위험이 있다. |
JOURNALED | 저널로그에 기록후에 성공응답을 반환한다. 서버가 비정상적으로 종료되더라도 이미 저널 로그에 기록되었기 때문에 다시 재시작하면 저널 로그의 데이터를 복구할 수 있다. 다만 단일 서버로 운영될 때는 발생하지 않던 문제가 레플리카 셋을 사용하는 경우 문제가 될 수 있다. 아래 '레플리카 셋 간의 동기화 제어' 에서 추가로 정리한다. |
FSYNC | 저널 로그뿐 아니라 데이터 파일까지 모두 디스크로 동기화하고 난 이후 클라이언트로 성공, 실패 여부를 반환하는 방식이다. FSYNC 는 저널로그를 지원하지 않던 시점에 도입된 방법인데, 비정상적인 종료로부터 데이터를 보호할 방법이 없어 데이터 파일 자체를 동기화하는 방식으로 데이터를 유지했다. |
레플리카 셋 간의 동기화 제어
레플리카 셋으로 구축한 경우 프라이머리 멤버가 비정상적으로 종료되거나 네트워크에 연결이 실패하면 세컨드리 멤버가 즉시 새로운 프라이머리로 선출된다.
그러나 새롭게 프라이머리로 선출된 멤버가 기존 프라이머리의 모든 OpLog 를 가져오지 못하면 어떻게 되는가 ?
상황을 예로들어본다.
새롭게 프라이머리로 선출된 B 멤버가 기존 프라이머리의 모든 OpLog 를 가져오지 못한 상태에서 기존 프라이머리 멤버A 가 연결할 수 없는 상태이면 MongoDB 서버가 선택할 수 있는 것이 없다.
즉, 최종 데이터를 가지고 있지 않지만, 그나마 레플리카 셋에서 살아남은 멤버 중에서 최신의 데이터를 가진 B 멤버가 새로운 프라이머리로 선출된다.
그리고 프라이머리 멤버로 선출됨과 동시에 클라이언트로부터 새로운 변경 요청을 받아 처리하게 된다.
시간이 흘러 기존 프라이머리였던 멤버A가 다시 네트워크에 연결되고 레플리카 셋에 다시 조인하면 A 멤버는 자신의 OpLog 와 새로운 프라이머리인 B 멤버의 OpLog 를 맞추는 작업을 하게 된다.
이 때 A 멤버는 장애가 발생했던 시점을 기준으로 볼 때 자신이 B 멤버보다 더 많은 데이터를 가지고 있던 것을 알게된다. 그러면 A 멤버는 더 가지고 있던 OpLog 를 모두 롤백하고 B 멤버에 있고 동기화시점까지였던 이후부터의 OpLog 를 동기화한다.
결국 A 멤버의 OpLog 일부는 손실되는데, 이 과정은 저널 로그를 동기화하고 있었는지 여부와 무관하게 작동한다.
이런 문제를 막기 위해 MongoDB 서버는 단순히 단일 노드의 WriteConcern 뿐 아니라 레플리카 셋 전체에 걸쳐 작동하는 모드가 필요해진 것이다.
레플리카 셋의 여러 노드에 대해 WriteConcern 을 설정하는 방법은 "{w : ?}" 옵션을 사용하는 것이다. 이 때 w 필드의 값은 숫자 또는 문자열을 설정할 수 있다.
옵션 | 설명 |
숫자 값 | 레플리카 셋에서 데이터를 동기화해야 할 멤버의 개수를 설정한다. 이 값을 2로 설정하면 레플리카 셋 멤버 중에서 자신을 포함해 2개의 멤버가 사용자의 변경 요청을 필요한 수준까지 처리했을 때 클라이언트로 성공 또는 실패 메시지를 반환한다. 즉 프라이머리 멤버와 세컨드리 멤버 중 1대가 정상적으로 처리해야 w:2 를 만족한다는 것이다. |
majority | w 옵션에 명시적으로 동기화할 레플리카 셋의 멤버 수를 설정하면 레플리카 셋의 개수가 변경될 때마다 응용 프로그램의 코드를 변경해야 할 필요가 있을 수 있다. majority 는 말 그대로 레플리카 셋을 몇 개의 멤버로 구성했는지와 관계없이 레플리카 셋 멤버 중 과반수가 동기화되면 클라이언트로 데이터 변경 요청 결과를 반환하는 writeConcern 옵션이다. 프라이머리 스취치로 인해 롤백될 가능성이 있는 데이터는 클라이언트로 보내지 않도록 ReadConcern 을 설정할 수 있다. ReadConcern 도 WriteConcern 과 동일하게 읽기를 실행할 멤버의 개수를 설정할 수 있으며 majorit 로 설정할 수도 있다. 하지만 데이터의 변경이 롤백으로 손실되지 않을 정도가 보장됐을 때 클라이언트로 결과를 반환하고 조회도 롤백되지 않을 데이터만 반환하도록 하려면 Read / Write Concern 을 모두 majority 로 설정해야 한다. 읽기 시 majority 이기 때문에 과반수 이상의 멤버에서 동기화된 데이터를 가져가게 된다. |
추가로 레플리카 셋을 구성하는 멤버들이 여러 IDC 에 걸쳐 배포된 경우에는 각 IDC 별로 레플리카 셋의 각 멤버에 태그를 할당할 수 있다.
WriteConcern 에 따른 응답 시간 비교
요청을 기다리지 않는 UNACKNOWLEDGED 모드는 매우 빠른 성능을 보여주나, MongoDB 의 응답을 받아야 하는 모드들은 낮은 성능을 보인다.
ACKNOWLEDGED 나 JOURNAL 과 비교했을 때 성능이 거의 절반으로 떨어지는 것을 확인할 수 있다.
Read Concern
MongoDB 서버의 레플리케이션을 Eventual Consistency (최종 동기화) 모델로 표현하기도 한다.
하지만 데이터를 읽어 가는 쿼리 입장에서는 최종 동기화가 수많은 문제점을 유발할 가능성이 있다.
이런 동기화 과정 중 데이터 읽기를 일관성 있게 유지할 수 있도록 MongoDB 서버에서는 ReadConcern 옵션을 제공한다.
ReadConcern 옵션은 WriteConcern 옵션과는 달리 레플리카 셋 간의 동기화 이슈만 제어한다.
MongoDB 서버의 ReadConcern 옵션은 다음 3가지 중 선택이 가능하다.
옵션 | 설명 |
local | local 모드의 ReadConcern 에서는 쿼리가 실행되는 MongoDB 서버가 가진 최신의 데이터를 반환하는 방식으로 작동한다. MongoDB 서버의 디폴트 ReadConcern 옵션인데 local 모드에서는 레플리카 셋의 다른 멤버가 가진 데이터 상태를 확인하지 않기 때문에 최신 데이터를 프라이머리 멤버만 가진 상태에서 프라이머리 멤버가 비정상적으로 종료되거나 연결이 끊어지면 그 데이터는 롤백 되어 Phantom Read 와 비슷한 상황이 발생한다. |
majority | 레플리카 셋에서 다수의 멤버들이 최신의 데이터를 가졌을 때만 읽기 결과가 반환된다. 레플리카 셋에서 다수의 멤버가 가진 데이터에 대해 쿼리 결과가 반환되므로 클라이언트가 읽었던 데이터가 롤백으로 인해 사라질 가능성은 상당히 낮다. 하지만 일부 레플리카 셋의 멤버가 동시에 연결할 수 없는 상태가되면 Phantom Read 현상이 발생할 수 있다. |
linearizable | ReadConcern 은 래플리카 셋의 모든 멤버가 가진 변경 사항에 대해서만 쿼리결과를 반환한다. 즉 클라이언트가 한번 읽어간 데이터는 이미 모든 레플리카 셋 멤버에 반영되었기 때문에 프라이머리가 스위칭된다 하더라도 절대 롤백되지 않는다. |
ReadConcern 이 majority 인 경우 MongoDB 서버는 레플리카 셋 멤버들의 복제 상태 표만 참조해서 클라이언트로 반환할 데이터를 판단하고 즉시 결과를 반환한다. 즉 특정 시점의 데이터를 반환하게 된다.
ReadConcern 이 linearizable 인 경우 MongoDB 서버는 자신이 가진 최신의 OpLog 까지 모든 세컨드리로 전파될 때까지 기다렸다가 결과를 반환하기 때문에 majority 보다 더 최신의 반환하나 응답이 훨씬 느려지게 된다.
그래서 linearizable 모드를 사용하는 경우에는 반드시 쿼리의 타임아웃 시간을 설정하는 것이 좋다.
majority 모드의 ReadConcern 을 사용하려면 반드시 다음과 같이 MongoDB 서버의 enableMajorityReadConcern 옵션이 활성화된 상태로 시작해야 한다. 레플리카 셋이 프로토콜 버전1 이상을 사용해야 하며 WiredTiger 에서만 가능하다.
Read Preference
클라이언트의 쿼리를 어떤 MongoDB 서버로 요청해서 실행할 것인지 결정하는 옵션이다.
Read Concern 은 읽기의 일관성이 목적이지만, Read Preference 는 데이터 읽기로 인한 부하의 분산이 주목적인 경우가 많다.
클라이언트 드라이버는 5개의 Read Prefercne 모드를 지원하며 조회쿼리에만 영향을 미친다.
옵션 | 설명 |
primary | 기본값으로 프라이머리 멤버로만 쿼리를 요청한다. |
primaryPreferred | 가능하면 프라이머리 멤버로 쿼리를 전송한다. 하지만 레플리카 셋에 프라이머리 멤버가 없는 경우 세컨드리 멤버로 쿼리를 요청한다. |
secondary | 세컨드리 멤버로만 쿼리를 요청한다. |
secondaryPreferred | 쿼리를 요청할 수 있는 세컨드리 멤버가 없으면 프라이머리 멤버로 쿼리를 요청한다. |
nearest | 레플리카 셋에서 쿼리의 응답 시간이 빠른 멤버로 쿼리를 요청한다. |
MongoDB 메뉴얼에서는 세컨드리 읽기를 가능하면 사용하지 않는 것을 권장하고 있고
프라이머리 멤버만으로 충분히 처리할 수 있는 부하라면 굳이 세컨드리를 사용하지 않는것이 좋다.
하지만 무거운 배치작업이나 통계성 작업들은 복제 지연에 민감하지 않으므로 세컨드리 멤버를 사용해도 무방하다.
maxStalenessSeconds 설정
최대 허용 가능한 복제지연시간을 설정할 수 있다.
MongoDB 드라이버나 Mongo 라우터가 지정된 시간보다 복제 지연이 심한 경우에 해당 세컨드리 멤버를 접속 가능한 대상 서버 목록에서 제거하고 접속하지 못하도록 차단한다.
maxStalenessSeconds 옵션은 주기적으로 각 세컨드리 멤버의 마지막 쓰기 시점을 이용해서 복제 지연을 측정하고,
복제 지연이 maxStalenessSeconds 보다 큰 경우에 해당 멤버로의 연결을 사용하지 못하게 한다.
복제 지연은 레플리카 셋의 모든 멤버에 접속해 현재 opLog 의 최종 시각을 확인한 다음 프라이머리와 세컨드리 멤버 간의 시간 차이를 확인하는 방식으로 측정한다.
값은 최소 90초 이상으로 설정해야 하며 아래로는 에러가 발생한다.
샤딩 환경의 중복 도큐먼트 처리
샤딩이 적용된 MongoDB 서버는 내부적으로 데이터의 균등 분산을 위해 밸런서가 각 샤드의 청크를 이동시키는 과정이 반복된다.
이런 밸런싱 작업으로 인해 동일 도큐먼트가 2개의 샤드에 동시에 존재할 수도 있다.
두개 이상의 샤드에 같은 도큐먼트가 존재하는 경우는 청크 이동과 같은 일시적 상황 뿐 아니라 알수없는 오류로 인해 청크이동이 실패 혹은 라우터를 거치지않고 직접 MongoDB 서버에 데이터를 저장하는 경우에도 발생할 수 있다.
하지만 실제 여러 샤드 서버가 같은 도큐먼트를 가진 상태라 하더라도 실제 쿼리를 실행해보면 중복된 도큐먼트가 사용자에게 보이지는 않는다.
이는 MongoDB 서버가 쿼리를 처리하면서 사용자에게 결과를 반환할 때 자기 자신이 가진 청크 목록에 소속된 도큐먼트인지 검증하기 때문이다. 만약 검증에 통과되지 못하면 서버는 버리고 무시하게 된다.
그런데 항상 이런 체크를 수행하지 않는 점이 문제이다. 아래의 2가지 경우에만 샤드의 소유권을 체크한다.
- 쿼리의 샤드에 메타 정보 버전이 포함된 경우
- 쿼리의 조건에 샤드 키가 포함된 경우
샤딩이 적용된 MongoDB 서버는 청크가 이동될 때마다 컨피그 서버의 메타 정보가 변경되는데, 이때마다 메타 정보의 버전이 1씩 증가한다. 즉 한 시점의 청크 분배 상태는 하나의 버전을 가지게 된다.
서버로 쿼리를 요청할 때 이 버전을 같이 전송하는데 MongoDB 서버는 그 버전의 청크 분배 상태에 맞게 자기 자신의 처리 결과를 필터링해서 결과를 반환한다.
두번째 경우는 특정 샤드로만 쿼리가 전달되므로 결과적으로 소유권을 체크하는 과정이 자연적으로 포함된다.
'Database > MongoDB' 카테고리의 다른 글
[MongoDB] 쿼리 개발과 튜닝(1) (6) | 2024.10.06 |
---|---|
[MongoDB] 데이터 모델링 (1) | 2024.09.19 |
[MongoDB] 인덱스(3) (0) | 2024.08.12 |
[MongoDB] 잠금과 트랜잭션(1) (0) | 2024.08.09 |
[MongoDB] 인덱스(2) (0) | 2024.08.02 |