모종닷컴

[MongoDB] Transaction 본문

Programming/데이터베이스

[MongoDB] Transaction

모종 2022. 10. 23. 15:18
반응형

Transaction

https://path81.com/post/what-it-means-for-a-transaction-to-be-transparent

먼저 트랜잭션이란 연속되는 연산들을 묶은 단위입니다. 이 단위에 정의된 연속된 연산들이 모두 성공해야 트랜잭션이 성공하며, 이 연산들 중 일부가 실패하면 정의된 연산들은 모두 실행되지 않았던 시점으로 다시 돌아가야 합니다. 예를 들어 A가 B에게 M원을 송금하는 것을 트랜젝션으로 정의한다면 아래와 같은 연산들이 포함될 것입니다.

A가 B에게 M원을 송금한다

  1. B의 계좌 상태가 유효한지 체크
  2. A의 계좌에서 M원을 차감할 수 있는지 체크
  3. A의 계좌에서 M원을 차감
  4. B의 계좌에 M원을 더한다.
  5. 연산이 제대로 이루어졌는지 체크
  6. 트랜잭션 완료.

이러한 연산을 실행하는 중 4번 연산(=B의 계좌에 M원을 더하는 연산)에서 이 알 수 없는 오류에 실패하였을 때 A의 계좌에서 M원을 차감했던 것을 다시 돌려놔야 하므로 실패하기 전 연산들을 적용하기 이전 상태로 모두 되돌려놔야 합니다.

MongoDB Transaction

몽고디비에서도 이러한 트랜젝션을 지원합니다. 정확히는 4.0 버전 이상부터 트랜젝션을 지원하기 시작했습니다. 추가로 4.0 버전의 몽고 DB를 사용한다고 해서 무조건 트랜젝션을 지원하는 것도 아닙니다. 몽고 DB가 replication으로 구성되어 있어야지 이 트랜젝션을 사용할 수 있습니다.

몽고 DB를 replication으로 구성하는 방법은 [MongoDB] Replication 포스팅을 참조하시면 됩니다. 긴 설명 없이 만들어놓은 글이라 빠르면 5분 이내에 구성이 가능하니 한번 시도해보시길 바랍니다.

트랜젝션 테스트(1) : commit

MongoDB replication 구성이 끝났다면 간단한 테스트를 통해서 트랜잭션이 동작하는지 확인해보도록 하겠습니다. Primary 인스턴스에서 세션을 두 개 열어서 테스트하도록 하겠습니다. 각 세션은 P1, P2라고 부르도록 하겠습니다.

먼저 테스트를 위한 데이터를 먼저 세팅해놓겠습니다.

P1

replset:PRIMARY> db.user.insertMany([{"name":"test1"}, {"name":"test2"}])
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("6354d6c3b6a6de20d03f8b92"),
		ObjectId("6354d6c3b6a6de20d03f8b93")
	]
}
replset:PRIMARY> db.user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }

P2

replset:PRIMARY> db.user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }

P1, P2 모두 동일하게 조회가 이루어지고 있습니다. 이제 본격적으로 트랜잭션을 다루어 보도록 하겠습니다.

 

P1

replset:PRIMARY> var session = db.getMongo().startSession()
replset:PRIMARY> var user = session.getDatabase('test').getCollection('user')
replset:PRIMARY> session.startTransaction({"readConcern": { "level": "snapshot" },"writeConcern": { "w": "majority" }})

P1에서 먼저 세션을 열고 트랜잭션을 시작하였습니다. 이 상태에서 user collection에 데이터를 하나 추가하고 조회해보도록 하겠습니다.

replset:PRIMARY> user.insert({"name":"test3"})
replset:PRIMARY> user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }
{ "_id" : ObjectId("6354d866b6a6de20d03f8b94"), "name" : "test3" }

 

 

추가한 데이터가 제대로 조회되고 있습니다. 그렇다면 P2에서는 이 시점에 조회 결과가 어떻게 나올까요?

P2

replset:PRIMARY> db.user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }

P2에서는 P1의 트랜잭션이 commit 되지 않은 상태이기 때문에 test3 데이터를 볼 수 없습니다. 이제 P1에서 커밋을 하고 다시 한번 조회해보도록 하겠습니다

P1

replset:PRIMARY> session.commitTransaction()
replset:PRIMARY> user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }
{ "_id" : ObjectId("6354d866b6a6de20d03f8b94"), "name" : "test3" }

P2

replset:PRIMARY> db.user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }
{ "_id" : ObjectId("6354d866b6a6de20d03f8b94"), "name" : "test3" }

이제는 P2에서도 test3 데이터를 볼 수 있게 되었습니다. 

트랜젝션 테스트(2) : rollback

다음에는 rollback을 테스트해보도록 할게요. 

P1

replset:PRIMARY> session.startTransaction({"readConcern": { "level": "snapshot" },"writeConcern": { "w": "majority" }})
replset:PRIMARY> var user = session.getDatabase('test').getCollection('user')
replset:PRIMARY> user.insert({"name":"test4"})
WriteResult({ "nInserted" : 1 })
replset:PRIMARY> user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }
{ "_id" : ObjectId("6354d866b6a6de20d03f8b94"), "name" : "test3" }
{ "_id" : ObjectId("6354d98cb6a6de20d03f8b95"), "name" : "test4" }

여기까지는 위와 동일합니다. 하지만 이 시점에 commit 대신 rollback을 시키도록 하겠습니다.

replset:PRIMARY> session.abortTransaction()
replset:PRIMARY> user.find()
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b92"), "name" : "test1" }
{ "_id" : ObjectId("6354d6c3b6a6de20d03f8b93"), "name" : "test2" }
{ "_id" : ObjectId("6354d866b6a6de20d03f8b94"), "name" : "test3" }

추가된 데이터가 반영이 되지 않고 롤백이 잘 되었음을 알 수 있습니다.

마무리

이번 포스팅에서 간단하게 MongoDB의 Transaction을 사용해보았는데요. 다음번에는 MongoDB의 read concern, write concern 옵션을 좀 보려고 합니다. 몽고의 이러한 옵션을 잘 모르고 쓰려니 테스트할 때 좀 당황스럽더라고요. 또한 위 옵션들을 정리하기 이전에 스프링에서 몽고DB의 트랜잭션을 사용하기 위한 설정도 곧 포스팅을 할 예정입니다. 

조금은 지루할 수 있는 글을 읽어주셔서 감사합니다. 

반응형