AWS 기술 블로그

Aurora MySQL와 MS-SQL의 성능 비교 직접 해보기

이 글은 SQL Server to Amazon Aurora MySQL in Game Development 시리즈 블로그의 일부로 작성되어 있습니다. 시리즈의 모든 글들은 아래 링크들을 따라가시면 읽어보실 수 있습니다.

일반적으로 Aurora MySQL의 성능은 MySQL 보다 3~4배정도 우수하다고 알려져 있습니다. 이는 벤치마크 테스트와 실제 운영 환경에서도 여러 차례 검증됐습니다. 하지만 한국 게임 서비스에서 많이 사용되어온 MS-SQL와의 성능 비교는 다루기 힘들었습니다. 서로 다른 제품을 비교하는 것이 쉽지 않기 때문입니다. 이에 기존에 MS-SQL로 개발된 게임을 그대로 Aurora MySQL로 이전했을 때를 가정하여, 게임과 유사한 테이블 구조, MS-SQL에서 많이 사용되는 저장 프로시저를 만들어두고 이를 Aurora MySQL로 바꾸어 성능 비교를 해보았습니다.

테스트 환경은 다음과 같습니다

<테스트 환경 아키텍처>

테스터가 명령을 실행하면 여러 대의 EC2 인스턴스가 각각 대상으로 하는 데이터베이스에 접속하여 부하를 주고, 이를 CloudWatch 지료와 로그로 비교할 수 있는 아키텍처 입니다.

데이터베이스 스키마는 다음과 같이 실제 MMORPG 게임에서 사용할 만한 구조입니다. 계정, 캐릭터, 재화, 아이템, 가챠로 뽑는 영웅, 퀘스트, 업적 등등이 존재하는 데이터 구조입니다.

<테스트 데이터베이스 스키마>

최초의 스키마와 이를 쿼리하는 저장 프로시저를 MS-SQL Server에서 작성 한 뒤, Aurora MySQL 로의 마이그레이션 작업을 산정하여 AWS Schema Conversion Tool을 사용하여 MySQL schema로 변경하였습니다.

인스턴스 사이즈는 r6i.2xlarge 와 r6i.4xlarge 두 가지 타입을 선택했습니다. 튜닝 파라미터들은 대체로 AWS에서 제공하는 기본 값을 사용하였으나 몇 가지 변경한 부분도 있습니다. MS-SQL 의 경우 기본 값의 IOPS/Throughput 파라미터를 사용하면 주기적인 백업 작업시에 성능 저하가 발생 할 수 있기 때문에 IOPS는 10,000으로, Throughput은 500MiBps로 늘려주었습니다.

또한, 일반적인 게임 워크로드에서는 라이센스 비용 절감을 위해 MS-SQL의 Enterprise 버전을 사용하는 경우는 흔하지 않기 때문에 Standard 버전으로 설정하였습니다. Multi-AZ 옵션 역시 고가용성 확보를 위해 반드시 켜야하지만, 비용 이슈로 Single-AZ 만을 사용하는 경우도 있기 때문에 MS-SQL 의 경우는 Single-AZ, Multi-AZ 옵션으로 설정한 인스턴스를 준비했습니다.

Aurora MySQL은 기본적으로 3개의 가용영역에 데이터를 저장하는 고가용성 분산 스토리지를 사용하기 때문에 이를 그대로 적용하였고, 읽기 적용 복제본 1개까지 추가하였습니다.

SQL Server RDS Aurora MySQL
인스턴스 타입 r6i.2xlarge / 4xlarge r6i.2xlarge / 4xlarge
스토리지 범용 SSD(gp3) 1024GB
10,000 IPOS
500MiBps Throughput
1024GB
배포설정 Standard Edition
Single-AZ / Multi-AZ
I/O Optimized
Multi-AZ
1 Read Replica
총 4기 총 2기

<테스트 인스턴스 설정>

테스트 시나리오는 다음과 같습니다.
<테스트 환경 아키텍처>에서 테스터의 실행 명령을 받으면 5대의 EC2에서 게임 클라이언트 역할을 하는 스크립트를 실행시켜서 계정을 접속하고, 캐릭터를 생성하고, 게임에 접속 한 뒤, 아이템 뽑기, 몬스터를 잡아서 경험치/아이템/재화 얻기 및 퀘스트 달성 등의 데이터베이스 액션을 일으킵니다.
이런 행동을 300ms 간격으로 100회씩 실시하게 했으며 일정 확률에 따라 차이를 두었습니다.
이 일련의 행동이 끝나면 접속을 종료했다가 처음부터 다시 수행하는 것으로 일정 시간동안 반복하고 데이터베이스의 성능을 로깅했습니다.

<테스트 플로우 차트>

데이터베이스의 쿼리는 다음과 같이 MS-SQL를 사용하는 게임 워크로드에서 많이 사용하는 저장 프로시저를 사용하였으며, 이를 MySQL로 컨버팅하여 사용했습니다.

CREATE PROCEDURE dbo.spCharacterLogin 
    -- Add the parameters for the stored procedure here
    @AccountUid bigint,
    @CharUid bigint
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;
    -- Insert statements for procedure here
    IF NOT EXISTS( SELECT 1 FROM Character WHERE AccountUid = @AccountUid AND CharUid = @CharUid )
    BEGIN
        RETURN -9999
    END
    BEGIN TRAN
    UPDATE Character SET LoginDate = GETDATE() WHERE AccountUid = @AccountUid AND CharUid = @CharUid
    IF @@ERROR != 0
    BEGIN
        ROLLBACK TRAN
        RETURN -1
    END
    --Check Time-limit items and remove
    UPDATE Inventory SET DeleteDate = GETDATE(), DeleteReason = 999 WHERE CharUid = @CharUid AND DeleteDate IS NULL AND (ExpireDate IS NOT NULL AND ExpireDate < GETDATE())
    IF @@ERROR != 0
    BEGIN
        ROLLBACK TRAN
        RETURN -1
    END
    --Check Time-limit post items and remove
    UPDATE Post SET DeleteDate = GETDATE() WHERE CharUid = @CharUid AND DeleteDate IS NULL AND ExpireDate IS NOT NULL AND ExpireDate < GETDATE()
    IF @@ERROR != 0
    BEGIN
        ROLLBACK TRAN
        RETURN -1
    END
    COMMIT TRAN
    SELECT ItemUid, Slot FROM Equipment WHERE CharUid = @CharUid

    SELECT ItemUid, ItemTid, Type, Level, Exp, Count FROM Inventory WHERE CharUid = @CharUid AND DeleteDate IS NULL AND (ExpireDate IS NULL OR ExpireDate > GETDATE())
    SELECT CurrencyUid, CurrencyTid, Value FROM Currency WHERE CharUid = @CharUid
    SELECT HeroUid, HeroTid, Grade, Level, Exp, Enchant FROM Hero WHERE CharUid = @CharUid AND DeleteDate IS NULL
    SELECT QuestUid, QuestTid, Category1, Category2, Value1, Value2, State FROM Quest WHERE CharUid = @CharUid
    SELECT AchieveUid, AchieveTid, Category1, Category2, Value1, Value2, State FROM Achievement WHERE CharUid = @CharUid
    SELECT CollectionUid, CollectionTid, Type, State, Value1, Value2, Value3, Value4, Value5, Value6 FROM Collection WHERE CharUid = @CharUid
    SELECT PostUid, PostTid, RewardTid, RewardValue, isReaded, ExpireDate FROM Post WHERE CharUid = @CharUid AND DeleteDate is NULL AND (ExpireDate IS NOT NULL AND ExpireDate > GETDATE())
    RETURN 0
END
GO
SQL

<MS-SQL 테스트용 저장 프로시저 spCharacterLogin>

DELIMITER $$
USE `mockdb`$$
CREATE DEFINER=`admin`@`%` PROCEDURE `spCharacterLogin`(in par_AccountUid BIGINT, in par_CharUid BIGINT, OUT ReturnCode INT)
return_label:
BEGIN
    DECLARE `@@ERROR` INT DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION

        BEGIN
            SET `@@ERROR` := 1;
        END;
    /* SET NOCOUNT ON added to prevent extra result sets from */
    /* interfering with SELECT statements. */
    /* Insert statements for procedure here */
    IF NOT EXISTS (SELECT
            1
            FROM `character`
            WHERE AccountUid = par_AccountUid AND CharUid = par_CharUid) THEN
        SET ReturnCode := - 9999;
        LEAVE return_label;
    END IF;
    START TRANSACTION;
        UPDATE `character`
        SET LoginDate = NOW(3)
        WHERE AccountUid = par_AccountUid AND CharUid = par_CharUid;

    /* Check Time-limit items and remove */
        UPDATE inventory
        SET DeleteDate = NOW(3), DeleteReason = 999
        WHERE CharUid = par_CharUid AND DeleteDate IS NULL AND (ExpireDate IS NOT NULL AND ExpireDate < NOW(3));

    /* Check Time-limit post items and remove */
        UPDATE post
        SET DeleteDate = NOW(3)
        WHERE CharUid = par_CharUid AND DeleteDate IS NULL AND ExpireDate IS NOT NULL AND ExpireDate < NOW(3);
    COMMIT;

    IF `@@ERROR` != 0 THEN
        SET `@@ERROR` := 0;
        ROLLBACK;
        SET ReturnCode := - 1;
        LEAVE return_label;
    END IF;
        SELECT
            ItemUid, Slot
            FROM equipment
            WHERE CharUid = par_CharUid;

    IF `@@ERROR` != 0 THEN
        SET `@@ERROR` := 0;
        ROLLBACK;
        SET ReturnCode := - 1;
        LEAVE return_label;
    END IF;
        SELECT
            ItemUid, ItemTid, Type, Level, Exp, Count
            FROM inventory
            WHERE CharUid = par_CharUid AND DeleteDate IS NULL AND (ExpireDate IS NULL OR ExpireDate > NOW(3));
        SELECT
            CurrencyUid, CurrencyTid, Value
            FROM currency
            WHERE CharUid = par_CharUid;
        SELECT
            HeroUid, HeroTid, Grade, Level, Exp, Enchant
            FROM hero
            WHERE CharUid = par_CharUid AND DeleteDate IS NULL;
        SELECT
            QuestUid, QuestTid, Category1, Category2, Value1, Value2, State
            FROM quest
            WHERE CharUid = par_CharUid;
        SELECT
            AchieveUid, AchieveTid, Category1, Category2, Value1, Value2, State
            FROM achievement
            WHERE CharUid = par_CharUid;
        SELECT
            CollectionUid, CollectionTid, Type, State, Value1, Value2, Value3, Value4, Value5, Value6
            FROM collection
            WHERE CharUid = par_CharUid;

    IF `@@ERROR` != 0 THEN
        SET `@@ERROR` := 0;
        ROLLBACK;
        SET ReturnCode := - 1;
        LEAVE return_label;
    END IF;
        SELECT
            PostUid, PostTid, RewardTid, RewardValue, IsReaded, ExpireDate
            FROM post
            WHERE CharUid = par_CharUid AND DeleteDate IS NULL AND (ExpireDate IS NOT NULL AND ExpireDate > NOW(3));
    SET ReturnCode := 0;
    LEAVE return_label;
END$$

DELIMITER ;
SQL

<MySQL 테스트용 저장 프로시저 spCharacterLogin>

그 외에 테스트에 활용된 MockServer 소스와 아키텍처는 아래 링크에서 참고 할 수 있습니다 :
https://github.com/aws-samples/mmorpg-mockup-db-server

실제 테스트는 <테스트 환경 아키텍처>에 명시한 것처럼 AWS Step-function을 활용했습니다. AWS Step-function은 사용자가 지정한 작업들을 지정한 순서대로 실행시켜주는 서버리스 워크플로 오케스트레이션 서비스입니다(https://aws.amazon.com/ko/step-functions/).
이 테스트에서는 Powershell command를 실행하여 EC2의 테스트 프로그램을 실행하고, 해당 프로그램이 종료되면 결과를 집계한 뒤, 다음 테스트를 위해 DB를 초기화하는 작업들을 순서대로 진행하도록 설정했습니다.

다음은 이렇게 측정한 벤치마크 테스트의 응답속도를 측정한 결과입니다.

<벤치마크 테스트 결과1 – 응답속도>

응답에 대한 지연 시간을 평균 값(ms 단위)으로 나타낸 그래프입니다. 대부분의 경우에서 큰 차이가 없었고, 1500개 클라이언트의 동시 접속 테스트 상황에서 약간의 차이가 있습니다.

다음은 벤치마크 테스트의 처리량을 정리한 결과입니다.

<벤치마크 테스트 결과2 – 처리량>

초당 저장 프로시저를 호출 횟수를 나타난 그래프입니다. 1000, 1500개의 클라이언트 동시 접속 테스트 상황에서 MS-SQL이 미세하게 우세한 성능을 보이지만 큰 차이는 없습니다.

하지만 위 테스트 시나리오에서 비용은 어떤지 살펴보겠습니다. 다음은 MS-SQL을 Single-AZ로 구성했을 때와 Aurora의 기본 구성간의 비용 차이를 보여주는 그래프입니다.

<월 비용 비교 – Single-AZ 구성>

On-Demand 일 때, EC2에 MS-SQL을 직접 설치 하여 운영할 때는 Aurora와 비슷하지만, 완전 관리형 MS-SQL RDS 서비스를 사용 했을 때의 비용은 Aurora와 두배 가까이 차이가 납니다. 이 차이는 3년 약정을 맺었을 경우 더 크며, 이 때는 EC2에 MS-SQL을 직접 설치 했을 경우에도 Aurora의 비용이 더 저렴합니다.

다음은 Multi-AZ 구성을 했을 때의 비용 차이를 보여주는 그래프입니다.

<월 비용 비교 – Multi-AZ 구성>

MMORPG 같은 무중단 서비스가 안정적으로 지속해야되는 대형 스케일의 게임에서 데이터베이스의 고가용성은 선택이 아닌 필수일 것입니다. 위 비용은 AWS 사이트에서 제공하는 비용 계산기를 사용하여 산출한 것으로 실제 구매 시에는 몇 가지 할인 옵션 등이 더해져 다소 비용 차이가 있을 수 있습니다만, 상대적으로 MS-SQL 의 비해 Aurora의 비용이 훨씬 저렴한 것을 확인 할 수 있습니다.

결론

실제 게임 서비스와 유사한 데이터 구조와 저장 프로시저를 사용하여 MS-SQL과 Aurora의 성능 테스트를 했을 경우 응답 속도와 처리량에는 그리 큰 차이가 없었습니다. 기존의 MS-SQL로 구성된 게임 서비스를 Aurora로 이전하는데 성능은 큰 이슈가 되지 못할 것입니다. 오히려 비슷한 성능에서 훨씬 저렴한 비용으로 사용할 수 있는 Aurora가 더 좋은 선택지가 될 수 있을 것입니다. 앞으로도 다양한 환경에서의 테스트를 통해 더 많은 인사이트를 제공할 수 있도록 노력하겠습니다. 데이터베이스 선택에 도움이 되길 바랍니다.

채민관

채민관

넥슨에서 바람의나라, 테일즈위버 등의 게임 서버를 개발하고 운영했습니다. AWS 에서는 Professional Services 팀에서 엔터프라이즈 기업들의 현대화 프로젝트를 수행하였고, 현재는 Solutions Architect 팀에서 게임사 고객들의 클라우드 전이를 돕고 있습니다.

Hyunchang Sung

Hyunchang Sung

성현창 솔루션즈 아키텍트는 게임 고객들이 AWS에서 성공적으로 게임을 개발하고 운영할 수 있도록 지원하고 있습니다. 게임 업계에서 오랫동안 게임 서버 프로그래머로 일했던 경험을 살려 게임 개발 및 서비스 전반에서 고객들에게 필요한 도움을 드리고 있습니다.