MongoDB를 TPC-C 테스트를 통해서 전자상거래 업무에 적합한지 테스트해보려고 했습니다. 그러던 중, py-tpcc라는 오픈소스 테스트 프로그램을 발견해서 테스트해보려는데, 몽고 DB에서 트랜잭션 처리가 안된다고 에러가 납니다. 몽고 DB에서는 트랜잭션을 지원한다고 하는데, 어떤 내용인지 확인해 보고 어떻게 사용하는지 알아보도록 하겠습니다.
TPC-C 란?
TPC-C는 Transaction Processing Performance Council의 약자로, 데이터베이스 기반의 온라인 트랜잭션 처리 성능을 평가하는 벤치마크 시나리오로 다양한 종류와 복잡도의 트랜잭션을 모델링합니다. TPC-C는 다음과 같은 트랜잭션을 모델링합니다.
- 신규 주문 (New Order)
- 결제 (Payment)
- 주문 상태 변경 (Order Status Change)
- 배송 (Delivery)
- 재고 수준 변경 (Stock Level Change)
TPC-C는 각 트랜잭션의 분당 처리 건수를 측정하고, 이를 가격으로 환산하여 전체 시스스템의 성능을 나타냅니다. TPC-C는 1분당 최대 처리 건수를 표시하며, 이를 tpmC라고 합니다. 예를 들어, IBM 595 Server Model 9119-FHA라는 유닉스 서버는, tpmC가 6,085,166이 나왔습니다.
TPC-C는 서버의 CPU, 메모리, 디스크 등의 하드웨어 성능능뿐만 아니라, 네트워크 보정, 피크 타임 보정, I/O 부하 보정 등의 여러 가지 보정 계수와 적용 범위를 고려하여 산출됩니다. 따라서 TPC-C는 서버의 실제 성능과 다를 수 있습니다.
py-tpcc란?
py-tpcc는 Python로 TPC-C 벤치마크를 구현한 프로젝트입니다. py-tpcc는 MongoDB와 같은 NoSQL 데이터베이스를 사용하여 TPC-C의 트랜잭션을 재현하고, 성능을 측정합니다.
py-tpcc의 기본적인 사용법은 다음과 같습니다.
- py-tpcc 소스 코드를 다운로드합니다.
- drivers 디렉렉토리에 자신의 데이터베이스 드라이버를 만듭니다. 예를 들어, MongoDB를 사용한다면 mongodbdriver.py라는 파일을 만들고, MongodbDriver라는 클래스를 정의합니다.
- 클래스 안에서 AbstractDriver에 정의된 함수들을 구현합니다. 이 함수들은 트랜잭션을 실행하기 위한 입력 파라미터와 결과를 생성하고, 데이터베이스에 삽입하고, 결과를 반환합니다.
- pytpcc.py 파일에서 driver의 설정 파일과 데이터 로더를 지정하고, 실행 명령어를 입력합니다.
몽고DB 트랜잭션
MongoDB에서 단일 문서(Single Document)에 대한 작업은 원자적입니다. 여러 문서와 컬렉션에 걸쳐 정규화하는 대신 임베디드 문서와 배열을 사용하여 단일 문서 구조에서 데이터 간의 관계를 캡처할 수 있기 때문에 이러한 단일 문서 원자성은 많은 실제 사용 사례에서 분산 트랜잭션의 필요성을 없애줍니다. DBMS로 치면 하나의 레코드를 수정하는 것과 같다고 생각하시면 됩니다.
여러 문서(단일 또는 여러 컬렉션)에 대한 읽기 및 쓰기의 원자성이 필요한 상황의 경우, MongoDB는 분산 트랜잭션을 지원합니다. 분산 트랜잭션을 사용하면 여러 작업, 컬렉션, 데이터베이스, 문서, 샤드에서 트랜잭션을 사용할 수 있습니다.
몽고DB에서 트랜잭션은 전용 트랜잭션 API를 이용해서 구현할 수 있습니다.
몽고DB Transactions API
몽고DB 트랜잭션 API를 예를 들어 설명합니다. 아래 예는 트랜잭션 API의 주요 구성 요소를 강조합니다. 특히 콜백 API를 사용합니다.
콜백 API는 다음과 같습니다.
- 트랜잭션을 시작합니다.
- 지정된 작업을 실행합니다.
- 결과를 커밋합니다(또는 오류 발생 시 중단).
콜백 API는 특정 오류에 대한 재시도 로직을 포함합니다. 서버는 TransientTransactionError or UnknownTransactionCommitResult commit error가 발생한 후 트랜잭션을 다시 실행하려고 시도합니다.
MongoDB 6.2부터 서버는 트랜잭션이 캐시에 비해 너무 크다는 오류( TransactionTooLargeForCache )를 수신하면 트랜잭션을 다시 시도하지 않습니다.
IMPORTANT
- 사용 중인 몽고DB 버전에 맞는 몽고DB 드라이버를 사용하세요.
- 드라이버를 사용하는 경우 트랜잭션의 각 작업은 세션을 각 작업으로 전달해야 합니다.
- 다음의 세 가지를 사용해서 트랜잭션을 조작할 수 있습니다.
- 트랜잭션 내에서 Collection을 생성할 수 있습니다. 다음 링크를 참고하세요.
이 글에서는 몽고DB python 트랜잭션 예제를 첨부합니다. 다른 언어를 확인하고 싶으신 분들은 아래 링크를 눌러서 확인하시면 됩니다.
https://www.mongodb.com/docs/manual/core/transactions/#transactions-api
아래 예제의 uriString을 보시면 replicaSet 혹은 mongos가 되어있는 것을 보실 수 있습니다. 몽고 DB의 트랜잭션을 사용하기 위해서는 제약조건이 있습니다. 이 부분에 대해서는 아래의 몽고DB Transactions과 원자성(Atomicity)에서 알아보겠습니다.
# For a replica set, include the replica set name and a seedlist of the members in the URI string; e.g.
# uriString = 'mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/?replicaSet=myRepl'
# For a sharded cluster, connect to the mongos instances; e.g.
# uriString = 'mongodb://mongos0.example.com:27017,mongos1.example.com:27017/'
client = MongoClient(uriString)
wc_majority = WriteConcern("majority", wtimeout=1000)
# Prereq: Create collections.
client.get_database("mydb1", write_concern=wc_majority).foo.insert_one({"abc": 0})
client.get_database("mydb2", write_concern=wc_majority).bar.insert_one({"xyz": 0})
# Step 1: Define the callback that specifies the sequence of operations to perform inside the transactions.
def callback(session):
collection_one = session.client.mydb1.foo
collection_two = session.client.mydb2.bar
# Important:: You must pass the session to the operations.
collection_one.insert_one({"abc": 1}, session=session)
collection_two.insert_one({"xyz": 999}, session=session)
# Step 2: Start a client session.
with client.start_session() as session:
# Step 3: Use with_transaction to start a transaction, execute the callback, and commit (or abort on error).
session.with_transaction(
callback,
read_concern=ReadConcern("local"),
write_concern=wc_majority,
read_preference=ReadPreference.PRIMARY,
)
몽고DB Transactions과 원자성(Atomicity)
여러 문서(단일 또는 여러 컬렉션)에 대한 읽기 및 쓰기의 원자성이 필요한 상황의 경우, 복제본 세트 및 샤드 클러스터에서의 트랜잭션을 포함한 분산 트랜잭션을 지원합니다.
분산화된 트랜잭션은 원자적입니다. 분산 트랜잭션은 "All or Nothing"이라는 명제를 제공합니다:
트랜잭션이 커밋되면 트랜잭션에서 변경된 모든 데이터가 저장되고 트랜잭션 외부에서 볼 수 있습니다. 트랜잭션이 커밋될 때까지 트랜잭션에서 변경된 데이터는 트랜잭션 외부에서 볼 수 없습니다.
그러나 트랜잭션이 여러 샤드에 쓰는 경우, 모든 외부 읽기 작업이 커밋된 트랜잭션의 결과가 샤드 전체에 표시될 때까지 기다릴 필요는 없습니다. 예를 들어, 트랜잭션이 커밋되고 쓰기 1이 샤드 A에 표시되지만 쓰기 2가 샤드 B에 아직 표시되지 않는 경우, 읽기 수준 "local"에서 외부 읽기는 쓰기 2를 보지 않고 쓰기 1의 결과를 읽을 수 있습니다.
읽기 수준 "local"에서는 이전 데이터를 읽을 수 있습니다.
트랜잭션이 중단되면, 트랜잭션에서 변경된 모든 데이터는 표시되지 않고 삭제됩니다. 예를 들어, 트랜잭션에서 어떤 작업이 실패하면 트랜잭션이 중단되고 트랜잭션에서 수행된 모든 데이터 변경 사항이 표시되지 않고 삭제됩니다.
대부분의 경우 분산 트랜잭션은 단일 문서 쓰기보다 성능 비용이 더 많이 들고, 효과적인 스키마 설계를 방해해서는 안 됩니다. 많은 시나리오에서 비정규화된 데이터 모델(포함된 문서 및 배열)이 데이터와 사용 사례에 적합할 수 습니다. 즉, 많은 시나리오에서 데이터를 적절하게 모델링하면 분산 트랜잭션의 필요성을 최소화할 수 있습니다.
Production Considerations
이용가능한 배포
버전 4.0에서 MongoDB는 replica sets에서 다중 문서 트랜잭션(multi-document transactions)을 지원합니다.
버전 4.2에서는 분산 트랜잭션이 도입되어 샤드 클러스터(sharded cluster)에서 다중 문서 트랜잭션에 대한 지원이 추가되고 replica sets에서 다중 문서 트랜잭션에 대한 기존 지원이 통합됩니다.
MongoDB 4.2 배포(replica sets 및 샤드 클러스터)에서 트랜잭션을 사용하려면 클라이언트는 MongoDB 4.2용으로 업데이트된 MongoDB 드라이버를 사용해야 합니다.
참고
분산 트랜잭션 및 다중 문서 트랜잭션
MongoDB 4.2부터 이 두 용어는 동의어로 사용됩니다. 분산 트랜잭션은 샤드 클러스터 및 복제본 세트의 다중 문서 트랜잭션을 의미합니다. 다중 문서 트랜잭션(샤드 클러스터든 복제본 세트든)은 MongoDB 4.2부터 분산 트랜잭션이라고도 합니다.
몽고DB에서 트랜잭션을 사용하기 위해서는 replica sets나 shared cluster로 배포해야 합니다. standalone mongodb에서는 사용할 수 없습니다.
'DBMS' 카테고리의 다른 글
몽고DB standalone to replica sets (0) | 2023.12.18 |
---|---|
몽고DB Replica Set 알아보기 - mongodb replication (0) | 2023.12.18 |
몽고DB(MongoDB) 데이터베이스 생성 및 삭제 - 데이터 조작(삽입/갱신/삭제) (0) | 2023.12.14 |
우분투에서 몽고DB 테스트 하기 - mongosh 설치 및 연결 (0) | 2023.12.13 |
우분투에 몽고DB(MongoDB) 설치하기 (0) | 2023.12.12 |