Documentation index for AI agents

Game Engine

GS2-SDK for Game Engine의 트랜잭션 처리에 대해

트랜잭션이란

GS2에서의 트랜잭션 처리란, “소비 액션"과 “입수 액션"으로 정의된 리소스 교환 처리를 실행하는 덩어리를 가리킵니다. 트랜잭션을 발행하는 API에는 GS2-Exchange의 교환 실행 함수(Exchange), GS2-Showcase의 상품 구매 함수(Buy), GS2-Quest의 퀘스트 시작 함수(Start)와 같은 것들이 있습니다.

“소비 액션"은 플레이어에게 불리하게 작용하는 사용자 데이터 조작을, 반대로 “입수 액션"은 플레이어에게 이득이 되는 사용자 데이터 조작을 가리킵니다.

구체적으로 GS2-Inventory라면 아이템의 소비가 “소비 액션”, 아이템의 입수가 “입수 액션"이 됩니다. 조금 색다른 예를 들면, GS2-Inbox의 메시지 개봉 플래그를 세우는 조작이 “소비 액션”, 메시지에 첨부된 아이템을 수령하는 것이 “입수 액션"이 됩니다. GS2에서는 사용자 데이터를 다시 쓰는 트랜잭션을 발행하고 실행하는 것을 반복함으로써 게임 사이클을 실현한다고 이해하십시오.

트랜잭션과 스탬프 시트

GS2에서는 트랜잭션 처리를 “스탬프 시트"라는 이름으로 부르고 있었습니다. 그 때문에 코드나 문서 곳곳에서 그와 같은 표기를 볼 수 있습니다. 최근에는 이 명칭을 적극적으로 사용하지 않고 “트랜잭션 처리"라고 부르고 있지만, 같은 것을 가리킨다고 이해하십시오.

유사한 단어로 “스탬프 태스크"라는 명칭도 있는데, 이는 트랜잭션 내에 포함된 “소비 액션"을 가리키는 것이었습니다.

트랜잭션 실행

GS2-Exchange의 Exchange나 GS2-Showcase의 Buy를 호출하면, EzTransactionDomain이라는 객체가 반환됩니다. EzTransactionDomain에는 Wait 함수가 마련되어 있으며, 이 함수를 호출함으로써 트랜잭션 처리의 완료를 기다릴 수 있습니다. 다만, 트랜잭션 처리가 완료되기까지의 시간에 보장이 없으므로, 타임아웃을 구현하는 것을 강력히 권장합니다.

Wait 함수에는 all이라는 인수가 마련되어 있으며, 여기에 true를 지정함으로써 트랜잭션 처리 내에서 새로운 트랜잭션이 발행된 경우 해당 트랜잭션의 실행 완료도 함께 기다릴 수 있습니다.

Wait 함수를 호출하지 않는 경우에는 Gs2Domain::Dispatch를 정기적으로 호출해야 합니다.

Deep Dive

여기서부터는 트랜잭션 처리의 상세한 구현에 대해 설명합니다. 대부분의 개발자는 아래에 적힌 내용에 대해 이해할 필요가 없습니다.

소비 액션과 입수 액션의 실행 순서

트랜잭션에 포함된 “소비 액션"을 모두 실행하면 “입수 액션"을 실행할 수 있게 되도록 제어되어 있습니다. 그 때문에 실행 순서는 “소비 액션"이 먼저이고 “입수 액션"이 나중이 됩니다. 이는 치트 내성을 실현하기 위해 중요한 사양입니다.

이러한 처리 흐름을, 일본 기업이 결재를 진행하는 “품의"에 비유하여, 여러 직책자(마이크로서비스)에게 허가를 받고(소비 액션을 실행) 품의서에 도장을 받아, 모든 마이크로서비스로부터 허가를 받으면 정말로 하고 싶었던 일(입수 액션)을 실행할 수 있다는 데서 스탬프 시트라고 불렀습니다.

트랜잭션 실행

GS2가 제공하는 마이크로서비스는 트랜잭션에 포함된 “소비 액션"이나 “입수 액션"을 전달함으로써 처리를 실행하는 기능을 갖추고 있습니다. 그러나 트랜잭션에 포함된 액션을 어느 마이크로서비스의 어느 API에 전달해야 하는지는 액션의 종류에 따라 정해지며, 실행하는 것도 상당히 번거롭습니다.

여기서 GS2-Distributor 마이크로서비스가 활약합니다. GS2-Distributor는 트랜잭션 액션을 받으면, 액션의 내용에 따라 적절한 마이크로서비스의 API로 전송하는 처리를 갖고 있습니다. 이를 통해 여러분은 아무것도 신경 쓰지 않고 트랜잭션 데이터를 GS2-Distributor에 전달하는 것만으로 트랜잭션을 실행할 수 있습니다.

오류 처리

다음으로 고려해야 할 것은 오류 핸들링입니다. GS2는 다양한 마이크로서비스를 제공하고 있으며, 장애가 발생할 때는 마이크로서비스 단위로 발생할 수 있습니다. 즉, “소비 액션"의 실행에는 성공했지만 “입수 액션"의 실행에는 실패한 경우가 있을 수 있습니다. 이 경우 “입수 액션"이 성공할 때까지 재시도하지 않으면, 플레이어가 손해를 본 상태로 처리가 멈추게 됩니다.

트랜잭션 처리의 자동 실행

과거 GS2는 오류 처리를 게임 개발자에게 맡기고 있었습니다. 그러나 생산성이 높지 않은 오류 처리를 많은 GS2 이용자에게 맡기는 것은 적절하지 않기 때문에, 그 책임을 GS2가 담보하도록 한 것이 “트랜잭션 자동 실행” 기능입니다.

트랜잭션을 발행하는 기능을 가진 마이크로서비스의 네임스페이스 설정에는 반드시 “TransactionSetting"이라는 항목이 있습니다. 그리고 “TransactionSetting"에는 “EnableAutoRun"이라는 자동 실행을 활성화하기 위한 플래그가 존재하며, 현재는 매니지먼트 콘솔을 이용한 설정에서는 기본적으로 활성화됩니다.

트랜잭션의 자동 실행을 활성화하면, GS2-Showcase의 Buy와 같이 트랜잭션을 발행하는 API를 호출할 때, API는 트랜잭션 ID만을 응답하고 트랜잭션의 페이로드는 응답하지 않습니다. 대신 내부적으로 GS2-Distributor에 트랜잭션을 실행하도록 데이터가 전달됩니다. 그 후 GS2-Distributor는 전달받은 트랜잭션을 실행하고, 오류가 발생하면 재시도를 수행합니다.

이러한 메커니즘으로 동작하기 때문에, 일반적으로는 1초 이내에 처리가 완료되지만, 오류가 발생하면 재시도가 발생할 수 있으므로 트랜잭션 처리가 완료되기까지 걸리는 시간에 대한 보장은 없습니다.

자동 실행한 트랜잭션의 완료 대기·결과 취득

트랜잭션의 자동 실행을 활성화하면, 트랜잭션이 완료되었는지 미완료인지가 이대로는 불명확해집니다. 그래서 GS2-Distributor는 트랜잭션의 자동 실행이 완료되면 게임에 통지를 전송하는 구조를 갖고 있습니다.

이 기능을 이용하려면 GS2-Distributor에 통지를 발행할 GS2-Gateway의 네임스페이스를 설정하고, 게임은 GS2-Gateway의 네임스페이스에 대해 통지를 받기 위한 사용자 ID를 설정해야 합니다. 2023년 8월 이후에 생성된 프로젝트에서는 default라는 이름의 GS2-Distributor와 GS2-Gateway가 자동으로 생성되며, SDK에서 특별한 설정을 하지 않는 경우 이 네임스페이스를 사용하여 이러한 처리를 수행합니다.

통지 내용에는 “트랜잭션 ID"가 포함되어 있으며, “트랜잭션 ID"를 지정하여 트랜잭션의 실행 결과를 취득하는 API가 GS2-Distributor에 마련되어 있습니다.

여러 입수 액션의 실행

설명한 대로 “소비 액션"은 여러 개를 설정할 수 있지만 “입수 액션"은 하나만 설정할 수 있습니다. 이는 트랜잭션의 치트 내성에 대한 뒷받침이 “모든 소비 액션을 실행하면 입수 액션을 실행할 수 있다"는 구조에서 비롯되기 때문입니다. 이 구조 위에서 입수 액션이 여러 개 있으면 여러모로 복잡해지고 만다는 것입니다.

그러나 게임 내 리소스 증감에는 입수 액션이 여러 개 존재하는 경우가 다양한 케이스에서 발생할 수 있습니다. 그래서 GS2-JobQueue라는 마이크로서비스가 등장합니다. GS2-JobQueue는 “입수 액션"을 지연 실행하기 위한 구조를 제공하고 있습니다. 그리고 GS2-JobQueue에는 한 번의 API 호출로 최대 10개의 잡을 등록할 수 있습니다.

“GS2-JobQueue에 잡을 등록한다"는 입수 액션이 존재하며, 여러 개의 입수 액션을 설정한 경우에는 트랜잭션 발행 시 이 입수 액션으로 내부적으로 변환됩니다. 입수 액션의 종류가 10개를 초과하는 경우에는 “GS2-JobQueue에 잡을 등록하는 잡을 등록한다"는 형태로 변환되며, 최대 100개의 입수 액션을 하나의 트랜잭션에 포함시킬 수 있습니다. 2023년 8월 이후에 생성된 프로젝트에서는 default라는 이름의 GS2-JobQueue가 생성되며, 이 네임스페이스를 사용하여 처리하도록 되어 있습니다. “TransactionSetting"의 JobQueueNamespaceId에 네임스페이스 ID를 지정함으로써 임의의 큐를 이용할 수 있지만, 특별한 이유가 없다면 지정할 필요는 없습니다.

GS2-JobQueue의 실행

GS2-JobQueue의 실행에 대해서도, 트랜잭션과 마찬가지로 과거의 경위가 있습니다. GS2-JobQueue에 등록된 잡의 실행도 명시적으로 GS2-JobQueue에 등록된 잡의 실행 API를 호출해야 했습니다. 잡은 실행에 성공하면 잡 큐에서 삭제되며, 잡 실행 API의 반환값에는 잡 큐가 비었는지가 응답값에 포함되어 있기 때문에, 비워질 때까지 처리를 반복하도록 하는 것을 게임 개발자의 책임으로 맡기고 있었습니다.

잡의 실행에 실패하면 잡 큐에서 잡이 삭제되지 않기 때문에, 큐가 비워질 때까지 잡을 반복 실행함으로써 재시도를 손쉽게 구현할 수 있도록 했습니다.

GS2-JobQueue의 자동 실행

그러나 이 역시 생산성이 높지 않은 처리를 많은 GS2 이용자에게 맡기는 것은 적절하지 않기 때문에, 그 책임을 GS2가 담보하도록 한 것이 “잡의 자동 실행” 기능입니다. GS2-JobQueue의 네임스페이스 설정에는 잡 큐의 자동 실행 플래그가 마련되어 있으며, 현재는 기본적으로 활성화되어 있습니다.

트랜잭션과 마찬가지로, 자동 실행이 활성화되면 잡을 큐에 등록하는 것만으로 자동으로 처리가 시작되고, 실행이 완료되면 GS2-JobQueue의 네임스페이스에 설정한 GS2-Gateway에 “완료된 잡 ID"를 통지합니다. GS2-JobQueue는 “잡 ID"를 지정하여 잡의 실행 결과를 취득할 수 있습니다. 2023년 8월 이후에 생성된 프로젝트에서는 default라는 이름의 GS2-Gateway가 자동으로 생성되며, SDK에서 특별한 설정을 하지 않는 경우 이 네임스페이스를 사용하여 이러한 처리를 수행합니다.

EzTransactionDomain::Wait의 정체

자, 지금까지 내부 처리에 대해 길게 설명해 왔습니다. 마지막으로 EzTransactionDomain의 Wait가 무엇을 하고 있는지 다시 한번 생각해 봅시다.

GS2-Showcase::Buy를 호출하면 트랜잭션이 발행되며, 자동 실행이 활성화된 경우에는 GS2-Gateway로부터 발행된 트랜잭션 ID의 완료 통지가 도착하기를 기다립니다. 자동 실행이 비활성화된 경우에는 반환된 트랜잭션 데이터를 GS2-Distributor에 전달하여 실행합니다.

자동 실행인 경우에는 트랜잭션의 실행 결과를 GS2-Distributor로부터 취득하고, 자동 실행이 아닌 경우에는 명시적으로 GS2-Distributor를 호출한 결과를 사용하여 트랜잭션의 실행 결과를 취득합니다. 여기에 트랜잭션 발행 처리가 포함되어 있던 경우에는 새로운 EzTransactionDomain을 생성하고, all이 true인 경우에는 해당 EzTransactionDomain::Wait를 호출합니다.

실행한 트랜잭션의 “입수 액션"이 GS2-JobQueue로의 잡 등록이었던 경우에는 추가 처리가 있습니다. GS2-JobQueue의 자동 실행이 활성화되어 있던 경우에는 GS2-Gateway로부터 등록한 잡 ID의 실행 완료 통지가 도착하기를 기다리고, 자동 실행이 비활성화되어 있던 경우에는 명시적으로 GS2-JobQueue의 잡을 실행합니다. 잡의 실행 결과에 트랜잭션 발행 처리나 GS2-JobQueue로의 잡 등록이 있었던 경우에는 all이 true인 경우 해당 처리들의 완료도 함께 기다립니다.

Gs2Domain::Dispatch의 정체

GS2-Gateway로부터 GS2-Distributor의 트랜잭션 실행이나 GS2-JobQueue의 잡 실행 완료 통지를 받으면, 처리 결과에 따라 SDK가 가진 로컬 캐시를 다시 씁니다. 이를 통해 트랜잭션 처리로 인해 아이템 소지 수량이 변동한 결과를, 아이템 소지 수량 취득 API를 호출하지 않고도 최신 값으로 취득할 수 있게 됩니다.

EzTransactionDomain::Wait 안에서 GS2-Gateway로부터의 통지를 기다릴 때 Gs2Domain::Dispatch를 호출하고 있으므로, Wait를 사용하는 경우에는 명시적으로 Gs2Domain::Dispatch를 호출할 필요가 없습니다. 그러나 EzTransactionDomain::Wait를 호출하지 않거나, all 인수에 false를 지정하는 경우에는 Gs2Domain::Dispatch를 호출하지 않으면, 트랜잭션이나 잡 큐에 의한 실행 결과가 캐시에 반영되지 않습니다.

트랜잭션의 완료 대기·처리 결과를 취득하는 구현 예제

    var domain = await _application.Quest.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    );
    EzTransactionDomain result = await domain.EndAsync(
        isComplete: true,
        rewards: null,
        config: null
    );

    await result.WaitAsync(true);

    var domain2 = _application.Distributor.Namespace(
        namespaceName: "distributor-0001"
    ).Me(
        gameSession: GameSession
    ).TransactionResult(
        transactionId: result.TransactionId
    );
    EzTransactionResult result = await domain.ModelAsync();
    var domain = gs2.Quest.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    );
    var future = domain.EndFuture(
        isComplete: true,
        rewards: null,
        config: null
    );
    yield return future;
    if (future.Error != null)
    {
        onError.Invoke(future.Error, null);
        yield break;
    }

    EzTransactionDomain domain2 = future2.Result;
    yield return domain2.WaitFuture(true);

    var future2 = _application.Distributor.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).TransactionResult(
        transactionId: domain2.TransactionId
    ).Model();
    yield return future2;
    EzTransactionResult result = future2.Result;

EzTransactionDomain

서버사이드에서의 트랜잭션 자동 실행 기능을 이용하여 실행된 트랜잭션의 정보를 보유합니다.

Result

타입설명
transactionIdstring트랜잭션 ID
jobNamestringJobQueue에서 실행된 잡의 이름

TransactionSetting의 상세

트랜잭션 설정의 상세는 다음과 같습니다.

타입활성화 조건설명
enableAutoRunbool발행한 트랜잭션을 서버사이드에서 자동으로 실행할지 여부
 enableAtomicCommitboolenableAutoRun이 활성화되어 있을 때트랜잭션의 실행을 원자적으로 커밋할지 여부
  transactionUseDistributorboolenableAtomicCommit이 활성화되어 있을 때트랜잭션을 GS2-Distributor를 사용하여 비동기 처리로 실행
  acquireActionUseJobQueueboolenableAtomicCommit이 활성화되어 있을 때입수 액션을 실행할 때 GS2-JobQueue를 사용할지 여부
동일한 리소스를 조작하는 입수 액션이 여러 개 존재하는 경우에 사용합니다