Transaction을 사용하는이유는 은행 송금 서비스나, 예약 시스템처럼 여러 작업에 대하여 하나의 논리적인 단위로 묶어 원자성을 보장하기 위함이다.
RDBMS 같은 경우 lock을 사용하여 Transaction을 구현하지만,
MongoDB는 같은 오브젝트를 참고할 경우 에러를 발생한다. (4.2부터 에러가 발생했을 때, 에러를 발생시키고 retry를 시키는 부분을 내부에서 처리한다)
따라서 Transaction을 사용하면 성능이 저하되는 문제가 발생한다.
에러가 발생하고 복구하는 로직을 직접 구현한 코드
from pymongo import MongoClient
from pymongo.read_concern import ReadConcern
from pymongo.write_concern import WriteConcern
from pymongo.errors import ConnectionFailure, OperationFailure
import certifi
# 몽고디비는 트랜잭션을 추천하지 않는다.
# 몽고디비는 같은 오브젝트를 참조하면 에러를 발생시킨다. -> 복구에 대한 비용이 매우 크다
# 4.0 버전에서 직접 retry를 구현해본다.
conn = "mongodb+srv://mongodb_user:__PWD__@cluster0.v6fiw3s.mongodb.net/"
client = MongoClient(conn, tlsCAFile=certifi.where())
# client.test.orders.drop()
# client.test.inventory.drop()
# client.test.inventory.insert_one({"name": "pencil", "qty": 1000})
def update_orders_and_inventory(session):
orders = session.client.test.orders
inventory = session.client.test.inventory
with session.start_transaction(read_concern=ReadConcern('majority'), write_concern=WriteConcern(w='majority')):
order = 100
orders.insert_one(
{"name": "pencil", "qty": order},
session=session
)
inventory.update_one(
{
"name": "pencil",
"qty": {"$gte": order}
},
{
"$inc": {"qty": order * -1}
}
)
commit_with_retry(session)
def commit_with_retry(session):
while True:
try:
session.commit_transaction()
print("Transaction Commited.")
print(session.client.test.orders.find_one({"name": "pencil"}))
print(session.client.test.inventory.find_one({"name": "pencil"}))
break
except (ConnectionFailure, OperationFailure) as err:
if err.has_error_label("UnknownTransactionCommitResult"):
print("UnknownTransactionCommitResult, retrying commit operation...")
continue
else:
print("Error during commit...")
raise
def run_transaction_with_retry(transaction_func, session):
while True:
try:
transaction_func(session)
break
except (ConnectionFailure, OperationFailure) as err:
# retry가 필요한 오류가 발생한 경우 while문을 계속 진행한다.
if err.has_error_label("TransientTransactionError"):
print("TransientTransactionError, retryinh transaction...")
continue
else:
raise
# 세션을 열고 트랜잭션 실행한다.
with client.start_session() as session:
try:
run_transaction_with_retry(update_orders_and_inventory, session)
except:
raise
에러처리를 데이터베이스 내부에서 직접 처리하는 코드
from pymongo import MongoClient
from pymongo.read_concern import ReadConcern
from pymongo.write_concern import WriteConcern
import certifi
conn = "mongodb+srv://mongodb_user:__PWD__@cluster0.v6fiw3s.mongodb.net/"
client = MongoClient(conn, tlsCAFile=certifi.where())
# client.test.orders.drop()
# client.test.inventory.drop()
# client.test.inventory.insert_one({"name": "pencil", "qty": 1000})
def callback(session):
orders = session.client.test.orders
inventory = session.client.test.inventory
order = 200
orders.insert_one(
{"name": "pencil", "qty": order},
session=session
)
inventory.update_one(
{
"name": "pencil",
"qty": {"$gte": order}
},
{"$inc": {"qty": order * -1}},
session=session
)
with client.start_session() as session:
session.with_transaction(callback, read_concern=ReadConcern('majority'), write_concern=WriteConcern('majority'))
print(session.client.test.orders.find_one({"name": "pencil"}))
print(session.client.test.inventory.find_one({"name": "pencil"}))