ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] 스파르타) Node.js 게임서버 주특기 플러스 1주차 강의 수강, 프로그래머스 - 오프라인/온라인 판매 데이터 통합하기 풀이 (MySQL)
    TIL-sparta 2024. 6. 24. 19:07

     

     > Node.js 게임서버개발 주특기 플러스+ 1주차 OT에서 제시된 사전 학습 키워드에 대해서 간단한 개념 정리를 해보았다.

     

    학습 키워드: serialization, deserialization, protocol buffer, latency, latency mask, performance optimization, mysql, union

     

    1. 주특기 플러스+ 강의 사전 학습

    1) 직렬화/역직렬화 (serialization/deserialization):

     직렬화(serialization)란, 자료 구조를 읽기 쉬운 형태(byte stream)로 변환하는 작업을 말한다. JS에서 key-value로 묶인 객체 또는 배열 등을 파일 혹은 DB에 저장하거나 네트워크를 통해 전송할 때 JSON.stringify()를 이용하여 문자열로 변환하여 담아두는 직렬화 방식을 자주 사용한다. 역직렬화(deserialization)은 직렬화된 데이터를 다시 원상태로 복구하는 과정이다. 예를들어 JSON.stringify()로 직렬화된 데이터가 있다면 이를 다시 코드에서 읽을 때 JSON.parse()를 호출하여 객체나 배열 상태로 복원하는 작업이 역직렬화에 해당한다.

     

    2) 프로토콜 버퍼 (protocol buffer):

     마찬가지로 직렬화와 관련되어있다. 구글에서 개발한 오픈소스 직렬화 라이브러리인데, XML보다 빠르고, 작고, 심플하여 많이 사용된다고 한다. 여러가지 언어로 지원하기 위해서 데이터를 언어에 구애받지 않는 구조의 파일 형식(*.proto)으로 저장하고, protoc를 통해 컴파일할 때 언어에 맞는 class파일을 생성해준다고 한다. JS에서는 NPM의 protobufjs 라이브러리를 사용한다.

    Figure 1. protocol buffer flow diagram

     

    3) 지연 시간 (latency):

     패킷이 송신지에서 수신지 호스트까지 도달하는데 걸리는 시간을 말한다. 네트워크 내에서 발생하는 딜레이 또는 방해가 적을 수록 낮은 지연시간을 가지게 된다. 이 latency를 최소화 하는 것이 기능 최적화(performance optimization)의 핵심 목표 중 하나다.

    Figure 2. 크롬 개발자 도구를 통한 request 별 latency 확인

     

     네트워크 지연 시간(network latency)은 클라이언트의 요청이 서버에 도달하기까지 걸린 시간과 다시 서버에서 응답을 송신한 시점부터 클라이언트가 해당 응답을 수신하기까지 걸리는 시간(왕복 시간)의 합으로 측정한다.

     

     디스크 지연 시간(disk latency)은 서버 호스트가 요청을 수신한 후 응답을 보내기까지 걸린 시간을 의미한다.


    4) 레이턴시 마스킹 (latency masking):

     이전 웹소켓 과제들에서 검증로직을 구현하면서 겪었던 문제들에 대한 내용이다. 크롬 다이노 웹 소켓 서버(링크: repo, TIL) 개발 당시 게임 클라이언트에서 특정 동작이 일어날 때 서버에 해당 정보를 전송하고, 서버에서 처리한 뒤 클라이언트에 처리된 값을 보내줘서 클라이언트를 업데이트하는 방식으로 구현했었고, 클라이언트의 정보 업데이트가 즉각 이루어지지 않기 때문에 대표적으로 선인장과 아이템에 동시에 충돌할 때 아이템의 점수가 반영되지 않는다는 문제가 있었다.

     

     같은 맥락에서 멀티 플레이어 게임에서 플레이어의 위치 검증을 위해 이러한 방식이 적용된다면 유저는 키 입력과 실제 캐릭터 이동 사이의 latency를 체감하게 되어 게임이 느리게 반응한다는 느낌을 받을 것이다. 이러한 문제를 해결하기 위해 일반적으로 사소한 값들의 처리를 클라이언트에서 담당하는데, 플레이어의 위치가 온전히 클라이언트에서만 처리된다면 핵 유저 등이 클라이언트를 변조하여 속도를 임의로 조절한 경우를 대처할 수 없다.

     

     위와 같은 문제들을 다루는 것이 바로 latency masking이다. 어떻게 보면 눈속임 같은 것이라고 할 수 있는데, 예시로 클라이언트에서 움직임을 미리 처리하고, 서버에서 해당 움직임에 대한 커맨드를 받아 플레이어의 다음 위치를 클라이언트로 반환하면 클라이언트에서의 유저 위치가 다시 수정되는 방식이 있다고 한다. latency가 50ms 이하로 낮은 경우에만 사용된다고 한다. 또 다른 예시로는 디아블로나 스타크래프트에 적용되어있는 방식인데, "게임 서버 프로그래밍 교과서" 책에서는 이를 '일단 보여주고 나중에 얼렁뚱땅' 이라고 표현했다. 유저가 공격 커맨드를 입력했을 때 캐릭터의 공격 모션이 실행되는데, 서버 지연시간이 높을 때 응답이 오지 않으면 공격 모션만 실행되고 스킬이 나가지 않는 방식이다. 그럼 처리되지 않은 것 아니냐? 할 수 있는데, latency masking 의 핵심은 데이터가 어떻게 다뤄지느냐가 아니라 유저가 어떻게 느끼느냐에 있다. 책에서 설명하기를 일반적으로 유저들은 공격 키를 눌렀는데 캐릭터가 아무 반응이 없는 것보다 공격 모션이라도 실행되는 것을 더 괜찮게 느낀다고 한다.

     

    2. MySQL Union (프로그래머스, 131537 - 오프라인/온라인 판매 데이터 통합하기)

    1) 문제 설명 요약 (원문은 링크 참고):

     요약: 온라인 판매 목록을 담은 테이블 ONLINE_SALE과 오프라인 판매 목록을 담은 테이블 OFFLINE_SALE에서 2022년 3월에 판매된 모든 제품의 판매 날짜, 상품 ID, 유저 ID 및 판매량을 출력하되, 유저 ID 기록이 없는 OFFLINE_SALE의 데이터는 USER_ID를 NULL로 표시한다. 쿼리 결과는 날짜 오름차순, 상품 ID 오름차순, 판매량 오름차순 순으로 정렬한다.

     

    2) 풀이 과정:

     이번 문제 또한 여태까지 만난 문제와 조금 다른 형태였는데, 각각의 테이블에서 뽑아낸 데이터를 마지막에 합쳐서 출력하는 방식이다. 이 문제를 해결하기 위해 SQL의 UNION에 대해 알아보았다.

     

     UNION은 두 쿼리의 결과를 합치는 키워드다. 그냥 UNION만 사용할 수도 있고, UNION ALL을 사용할 수도 있는데, 전자는 두 데이터에서 중복을 제거해주지만, 후자는 중복된 데이터도 별개의 데이터로 취급하여 추가한다. UNION 외에도 INTERSECT와 EXCEPT라는 키워드가 존재하는데, 전자는 두 쿼리 결과의 교집합이 되는 데이터, 후자는 앞의 쿼리에서 뒤의 쿼리와 겹치는 데이터를 제거한 결과를 출력한다.

     

     문제에 이를 적용하면, ONLINE_SALE 테이블에서 3월 판매 목록을 받는 쿼리를 작성하고, OFFLINE_SALE 에서도 마찬가지로 쿼리하는데, 유저 ID를 NULL로 기록하도록 설정해줘야한다. 이후 이 두 쿼리를 UNION ALL 을 사용하여 합치면 된다.

     

    3) 정답 쿼리:

    SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE,
        PRODUCT_ID,
        USER_ID,
        SALES_AMOUNT
    FROM ONLINE_SALE
    WHERE SALES_DATE >= 20220301
        AND SALES_DATE < 20220401
    
    UNION ALL
    
    SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE,
        PRODUCT_ID,
        NULL AS USER_ID,
        SALES_AMOUNT
    FROM OFFLINE_SALE
    WHERE SALES_DATE >= 20220301
        AND SALES_DATE < 20220401
    
    ORDER BY SALES_DATE, PRODUCT_ID, USER_ID

     UNION으로 합쳐진 데이터는 같은 field 이름을 가진경우 해당 필드를 공유한다. 따라서 JOIN처럼 테이블이름.필드이름 형식으로 적는 과정이 없다. 이를 감안하여 마지막 줄에 ORDER BY 절을 추가해주면 된다.

     

     

    --

     

    REFERENCES:

     

    https://www.baeldung.com/cs/serialization-deserialization

     > Baeldung, "What is Serialization and Deserialization in Programming?" 

    https://www.honeybadger.io/blog/serialization-deserialization-nodejs/

     > HoneyBadger, "Serialization and deserialization in Node.js"

    https://bcho.tistory.com/1182

     > "구글 프로토콜 버퍼" by Terry Cho

    https://en.wikipedia.org/wiki/Protocol_Buffers

     > Wikipedia, "Protocol Buffers"

    https://protobuf.dev/overview/

     > Protobuf documentation, "Overview", Figure 1 이미지 출처

    https://developer.mozilla.org/en-US/docs/Web/Performance/Understanding_latency

     > MDN, "Understanding latency", Figure 2 이미지 출처

    https://thebook.io/006884/0301/

     > TheBook, "게임 서버 프로그래밍 교과서",  배현직 저, 5.3 레이턴시 마스킹

    728x90
Designed by Tistory.