Skip to content
This repository was archived by the owner on Jun 14, 2022. It is now read-only.

Latest commit

 

History

History
67 lines (35 loc) · 6.63 KB

db_transaction.md

File metadata and controls

67 lines (35 loc) · 6.63 KB

트랜잭션

개요

여러 장비에서 돌아가는 게임 서버들이 같은 데이터를 변경하려고 경쟁하는 상황에서느 데이터 무결성을 보장하기 쉽지 않습니다. DBMS를 단 하나만 사용한다면 DBMS가 제공하는 트랜잭션 기능을 이용하면 되지만, 여러 DBMS 장비에 나누어 저장되어 있는 데이터를 함께 변경하면서 데이터 무결성을 유지하려면 대단히 비용이 많이 듭니다.

실버바인 서버 엔진 2는 잠금(lock) 기반 비관적 동기화를 사용합니다. 엔진이 제공하는 API를 사용해서 프로그래밍하면, 데이터 무결성을 유지하면서 여러 도큐먼트와 글로벌 테이블에 흩어져 있는 데이터를 함께 변경할 수 있습니다.

잠금 단위

  • 도큐먼트 하나하나가 잠금 단위입니다.
  • 각 글로벌 테이블의 키 하나하나가 잠금 단위입니다.

※ 도큐먼트 안에 들어있는 특정 데이터만을 잠글 수는 없습니다.
※ 글로벌 테이블에 어떤 키를 갖는 행이 아직 존재하지 않더라도, 그 키를 잠글 수 있습니다.
※ 아직 생성하지 않은 도큐먼트는 잠글 수 없습니다.

트랜잭션 과정

데이터를 읽고쓰기 위해서는 Transaction 객체를 통해야 합니다. 아직 잠금을 획득하지 않은 데이터에 접근하려고 하면 엔진이 이를 감지하여 예외를 발생시키므로, 어떤 데이터를 건드릴 것인지 Transaction 객체에 미리 알려줘야 합니다.

트랜잭션을 시작할 때 그 트랜잭션이 건드릴 예정인 모든 잠금 단위(도큐먼트 id, 글로벌 테이블 키)를 Transaction 객체에 전달하여 잠금을 획득합시다. 이 잠금은 하나의 서비스에 참여하는 모든 게임서버가 함께 관리하는 것으로, 잠금을 획득한 트랜잭션이 끝나기 전까지는 잠근 데이터를 다른 곳에서 건드릴 수 없습니다.

Transaction 객체를 통해 데이터를 읽고 변경하는데, 이 내용은 실제 저장소에 즉시 반영되지 않습니다. 무엇을 어떻게 변경할지 모든 내용을 결정하고 나서 Commit()을 호출해야만 비로소 엔진이 DBMS에 쿼리를 발송합니다.

여러 개의 잠금 획득하기

하나의 LockSpec 객체에 여러 잠금 단위를 한꺼번에 지정하여 잠금을 획득하는 경우, 엔진이 LockEscalationOrder를 따라 잠금 순서를 자동으로 정렬해서 획득해줍니다. 어떤 데이터를 건드릴지 미리 알고 있는 경우, 트랜잭션을 시작할 때 이와 같은 방법으로 그 트랜잭션이 건드릴 수 있는 모든 대상을 엔진에 알려주는 것을 권장합니다.

그러나 글로벌 테이블을 조회해서 그로부터 도큐먼트 id를 얻는 등, 어떤 데이터를 건드릴지 트랜잭션을 일부 진행해야만 알게 되는 경우에는 트랜잭션 중간에 잠금을 추가로 획득해야 합니다. 단, 게임 전체에서 일관성 있는 순서로 잠금을 획득한다는 것을 보장하기 위해, 잠금 추가 획득이 가능한 관계를 미리 .schema 파일에 명시해야 합니다. 샘플에서 LockEscalationOrder를 검색해 보세요.

.schema 파일에서 명시적으로 허용하지 않은 순서로 잠금을 추가로 획득하려고 시도하면 엔진이 이를 감지하여 예외를 발생시킵니다. 이것은 데드락을 방지하기 위한 장치입니다.

트랜잭션 안에서 도큐먼트를 추가할 때

트랜잭션 안에서 도큐먼트를 새로 생성하면 LockEscalationOrder 설정과 무관하게 자동으로 잠금을 획득합니다. 새로 생성한 도큐먼트의 id를 다른 곳으로 전달하여 별도의 트랜잭션에서 잠금을 획득하려고 시도하지 않는 이상 데드락이 일어날 가능성은 없기 때문에 예외적으로 이런 규칙을 허용했습니다.

그러므로, 트랜잭션 안에서 새로 생성한 도큐먼트의 id는 트랜잭션이 끝나기 전에는 다른 곳에서 알 수 없도록 하십시오.

잠금은 가능한 한 바깥에서 획득하십시오

직관적으로 이해하기 어려울 수 있는 내용이나, 저희를 믿으십시오. 필요한 잠금을 그때그때 획득하도록 로직 코드를 작성하시면 지옥문이 열릴 것입니다.

호출 '되는' 함수에서는 원하는 데이터의 잠금을 이미 획득한 상태라고 가정하고 프로그래밍하셔야 합니다. 엔진이 잠금 획득 여부를 검사해줄 것이니 확인 작업은 엔진에 맡기시면 됩니다. 잠금 획득은 트랜잭션을 시작한 바로 그 호출 깊이, 혹은 한 단계 안 정도에서만 일어나야 합니다.

필요한 잠금을 직접 획득하는 코드는 자유롭게 불러 쓰기 대단히 어렵습니다(composable하지 않습니다). 일반적으로 이런 코드는 데드락을 유발하기 쉽습니다. 실버바인 서버엔진 2의 DB 잠금은 획득 순서를 엔진에서 검사해 주므로 실제로는 데드락 대신 개발 단계에서 LockEscalationOrder 위반 예외가 발생하게 됩니다만 안심하시면 안됩니다. 데드락은 일어나지 않더라도 유지보수하기 대단히 어려울 것입니다.

트랜잭션이 보장하는 내용

  • 하나의 도큐먼트에 대한 변경은 DBMS 트랜잭션을 이용해서 원자적으로 일어납니다.
  • 하나의 글로벌 테이블 키에 대한 변경도 DBMS 트랜잭션을 이용해서 원자적으로 일어납니다.
  • DBMS가 비정상 종료되거나, 트랜잭션을 실행하는 게임서버가 비정상 종료되거나, 게임서버와 DBMS 간의 연결이 비정상적으로 끊기는 경우, 전체 트랜잭션에서 일부 락 단위의 변경사항은 누락되고 일부만 커밋될 수도 있습니다.
  • 위 항목에서 언급한 경우를 제외하면 Transaction 객체를 통해 커밋한 내용은 모두 안전하게 저장됩니다.

높은 보호 수준의 트랜잭션 (미구현)

정전 등의 상황에도 트랜잭션의 일부분만 커밋되는 경우가 없도록 할 수 있습니다. 즉, 변경사항 전체가 커밋되거나 아니면 아무것도 커밋되지 않음을 보장하게 하는 것입니다. 데이터 무결성이 깨지는 일은 줄어들지만 기본 보호 수준보다 DB에 부하를 더 가하고 트랜잭션 커밋 절차가 오래 걸립니다. 게임 프로그래머가 성능과 데이터 보호 강도 사이에서 트레이드 오프를 선택하게 하는 것입니다.

일반적으로 게임에서 이 정도로 강력한 보호 수준이 요구되는 경우는 드물다고 판단하여 아직 구현하지 않고 있습니다.