SQL 2015. 3. 31. 15:58

DBMS 메모리 구조

링크 : 

  1. 1. 데이터 파일의 구조
  2. 2. DBMS 메모리 구조
  3. 3. 인덱스 기본
  4. 4. 정렬을 제거하는 방법
  5. 5. 프로시져 캐시 경합 확인하기
  6. 6. Loop 쿼리 제거하기
  7. 7. 튜닝 결과 샘플 보기





MS SQL의 시스템 공유 메모리 영역을 Memory Pool 이라고 한다.

여러 쓰레드가 동시에 액세스 할 수 있는 메모리 영역이다.

캐시 영역은 매우 다양한데 모든 DBMS가 공통적으로 사용하는 캐시 영역은 위와 같다. 

메모리를 공유하기 위해 액세스 직렬화 메커니즘이 사용된다.

MSSQL은 쓰레드 기반 아키텍쳐이기 때문에 Worker Thread 전용 메모리 영역이 없다. 쓰레드는 부모 프로세스의 메모리 영역을 사용한다.


DB 버퍼 캐시

 데이터 파일로부터 읽어 들인 데이터 Page를 담는 영역을 DB 버퍼 캐시라고 한다.

인스턴스에 접속한 모든 사용자 스레드는 Worker Thread를 통해 DB 버퍼 캐시의 버퍼 블록을 동시(내부적으로 버퍼 LOCK을 통한 직렬화)에 액세스할 수 있다

Direct Path Read 메커니즘이 작동하는 경우를 제외하면, 모든 페이지 읽기는 버퍼 캐시를 통해 이루어진다.

, 읽고자 하는 페이지를 먼저 버퍼 캐시에서 찾아본 후 없으면 디스크에서 읽는다. 디스크에서 읽을 때에도 먼저 버퍼 캐시에 적재한 후에 읽는다

데이터 변경도 버퍼 캐시에 적재된 페이지를 통해 이루어지며, 변경된 블록을 주기적으로 데이터 파일에 기록하는 작업은 Lazy writer 가 한다.

버퍼 블록의 상태

-       Free 버퍼: 비어있거나 (Clean 버퍼), 데이터가 있지만 파일과 동기화되어 덮어써도 무방

-       Dirty 버퍼: 캐시 된 후 변경이 발생했지만 동기화가 되지 않은 상태

-       Pinned 버퍼: 읽기 or 쓰기 작업이 진행 중인 버퍼 블록

데이터 파일 (디스크)  -> 로드 -> Free 버퍼 확보 -> 변경 -> Dirty로 상태 변경 -> 동기화 -> Free

 

LRU 알고리즘 (Least recently used)

캐시는 유한 자원, 사용 빈도가 높은 데이터 블록 위주로 캐시를 구성하는 알고리즘

모든 버퍼 블록 헤더를 LRU 체인에 연결 후 사용 빈도 순으로 정렬된 상태를 유지

FREE 버퍼가 필요한 순간에 액세스 빈도가 낮은 순으로 데이터 블록을 캐시에서 밀어낸다.




프로시저 캐시

LRU 알고리즘 사용

캐싱된 SQL과 생성계획의 재사용성을 높이는게 중요하다.

테이블, 인덱스, 파일그룹, 데이터 파일, Heap/Index 구조 오브젝트, 익스텐트, 사용자, 제약에 관한 메타 정보를 저장한다.





SQL 파싱 순서


실행 계획을 메모리 풀에 캐싱해 두는 이유는 하드 파싱 비용이 비싸기 때문이다.

실행 계획 경우의 수를 계산하는 방법은 테이블이 n개일 때 조인 경우의 수는 



해당 결과에 조인 종류, 스캔 경우의 수, 테이블 스캔 or 인덱스 스캔 여부 확인, 인덱스를 탄다면 어떤 인덱스를 탈지... 모든 경우의 수를 따지면 하드 파싱은 매우 비싸진다.

때문에 한번 만들어둔 실행 계획을 다시 사용하는 것을 소프트 파싱이라고 한다.

소프트 파싱의 키 값은 SQL 문장 그 자체 (문자열이 이름이다.) 이며 별도의의 SQL ID를 부여하는 DBMS의 경우도 SQL ID와 SQL 문자이 1:1로 대응한다.


소프트 파싱을 잘 활용하기 위해서는 실행 계획을 공유하지 못하는 경우를 이해할 필요가 있다.

- 공백 문자 또는 줄바꿈

SELECT * FROM USERDB;

SELECT * 

FROM USERDB;


- 대소문자 구분

SELECT * FROM USERDB;

SELECT * FROM userdb;


- 주석

SELECT * FROM USERDB;

SELECT * /* 모든 칼럼*/ FROM USERDB;


- 스키마 명시

SELECT * FROM USERDB;

SELECT * FROM dbo.USERDB;


- 조건절 비교값

SELECT * FROM dbo.USERDB WITH(NOLOCK) WHERE ID = '철수';

SELECT * FROM dbo.USERDB WITH(NOLOCK) WHERE ID = '영희';


위의 예시들 중 가장 큰 문제가 되는 경우는 마지막 경우이다. 만약 로그인 쿼리나 아이템 페이지 목록을 얻는 쿼리 등을 작성한다고 생각해보면 프로시저 캐시는 다른 이름의 동일한 기능을 하는 실행계획으로 가득찰 것이다. 위와 같은 쿼리를 Literal SQL 이라고 한다. Literal SQL은 프로시저 캐시 효율과 밀접한 관계가 있다.


http://databaser.net/moniwiki/pds/OracleServer/Oracle_Hard_Parsing.pdf




바인드 변수 사용하기

- 실행 계획을 캐시에 저장 후 실행 시점에 값을 바인딩

사용 시 파싱 소요 시간과 메모리 사용량이 감소한다. (CPU, 메모리 사용률)

리터럴 변수 변수 사용은 라이브러리 캐시 경합을 발생시켜 시스템 성능을 저하시킨다.


바인드 변수 사용 시 주의 사항

변수 바인딩 시점이 쿼리 최적화 후, 옵티마이저는 조건절 칼럼의 데이터 분포가 균일하다고 가정한다.

칼럼에 대한 히스토그램 정보가 프로시저 캐시에 있지만 사용할 수 없다. 때문에 균일하지 않을 때 성능이 다르게 나타날 수 있다.


바인드 변수 부작용 극복 노력

Parameter Sniffing - 첫 수행 시 바인드 값을 훔쳐보고 칼럼 분포를 이용해 실행 계획 결정하는 방법

첫 실행 값에 따라 성능이 달라지는 등의 문제가 있다. 보통 해당 기능은 사용하지 않는다.


https://technet.microsoft.com/ko-kr/library/ms191006(v=sql.105).aspx






SQL 2015. 3. 25. 17:27

카드 시스템 분석 자료

링크 : 

  1. 1. 데이터 파일의 구조
  2. 2. DBMS 메모리 구조
  3. 3. 인덱스 기본
  4. 4. 정렬을 제거하는 방법
  5. 5. 프로시져 캐시 경합 확인하기
  6. 6. Loop 쿼리 제거하기
  7. 7. 튜닝 결과 샘플 보기



개선 방법은 대부분 인덱스 수정 (순서 변경, 추가, 삭제)

더 좋은 해결 방법이 있다면 서버 비지니스 로직 변경 + 인덱스 & 쿼리 튜닝

Loop 쿼리는 One 쿼리로 변경 후 서버 비지니스 로직 변경 등..





내용 : 카드 삭제


문제 : 클러스터형 인덱스 파편화 심화 유발, 스레드당 최대 30번의 Execute Call 발생

개선방향 : 삭제 플래그 형식으로 변경, 삭제코드를 array로 넘긴 후 파싱 Execute Call 감소

 

쿼리 수정

-    삭제 대상 시퀀스를 문자열로 조합 후 쿼리에 전달 해당 쿼리는 파싱 후 처리

-    수정 후 Execute Call 은 스레드당 무조건 한 번





내용 : 경험치 증/


문제 : 조건문 적용 순서 오류, 불필요한 SELECT, 스키마 누락

개선 방향

-    조건문을 직관적으로 변경

-    UPDATE 구문 수정으로 첫번째 SELECT 제거

-    스키마 추가

결과

-    SELECT2 -> 1

-    수행 시간 12% 감소





내용 : 업그레이드 카드 목록 얻기 (레전드 만들기)

문제 : 랜덤 액세스 발생이 심각함

개선 방향 : 랜덤 액세스 제거 & 정렬 제거, 비클러스터형 인덱스 수정


결과

-    랜덤 액세스 제거

-    인덱스 순서를 변경하면 정렬 제거가 가능하다. (CharacterNO ASC 두 번째로)

-    해당 쿼리에 맞는 인덱스 추가 가능 ( INSERT I/O 비용 0.01 증가 )




내용 : 업그레이드 카드 목록 얻기 (특정 클래스만

문제 : 랜덤 액세스 발생 심각함

개선 방향 : 랜덤 액세스 제거 & 정렬 제거, 비클러스터형 인덱스 수정


결과

-    랜덤 액세스 제거

-    정렬 제거 (특정 클래스만 탐색하기 때문에 가능)

-    처리 비용 89% 감소




내용 : 캐릭터 카드 목록을 얻는다.

문제 : 랜덤 액세스 심각, 불필요한 업데이트 구문, 정렬

개선 방향 : 랜덤 액세스 제거, 불필요한 구문 제거, 비클러스터형 인덱스 수정


결과

-    랜덤 액세스 제거

-    UPDATE 구문 제거

-    처리 비용 46% 감소  (정렬 제거 시 58% 감소)




내용 : 강화 재료 카드 목록을 얻는다.


문제 : 보유 수량만큼 INNER JOIN, 클러스터형 인덱스와 조인, 랜덤 액세스, 정렬

       총 네 번의 INNER JOIN이 발생

개선 방향 : 불필요한 JOIN, 랜덤 액세스, 정렬 제거,  인덱스 수정


결과

-    INNER JOIN 감소 ( 4 -> 1)

-    랜덤 액세스 제거

-    정렬 제거

-    처리 비용 88% 감소



튜닝전 UPS_GetCharacterCardList_Enchant 실행 계획




튜닝 후 UPS_GetCharacterCardList_Enchant 실행 계획





운영툴 대회채널 결과 페이지 튜닝전 실행 계획




운영툴 대회채널 결과 페이지 튜닝 후 실행 계획





파란 박스로 체크한 결과가 튜닝 후 쿼리 실행 시 결과를 얻는데 걸리는 시간(ms)


SQL 2015. 3. 25. 17:01

데이터 파일의 구조

링크 : 

  1. 1. 데이터 파일의 구조
  2. 2. DBMS 메모리 구조
  3. 3. 인덱스 기본
  4. 4. 정렬을 제거하는 방법
  5. 5. 프로시져 캐시 경합 확인하기
  6. 6. Loop 쿼리 제거하기
  7. 7. 튜닝 결과 샘플 보기





Page

 I/O가 이루어 지는 단위, 데이터 R/W 논리적 단위

하나의 레코드에서 하나의 칼럼만을 읽으려고 해도 레코드가 속한 페이지 전체를 읽는다.

SQL 성능 지표 중 액세스하는 Page 수는 매우 중요하다. 옵티마이저의 판단에 가장 큰 영향을 준다.

MSSQL Page 크기는 8KB

 

Extent

 파일그룹으로부터 공간을 할당하는 단위

테이블이나 인덱스에 데이터를 입력하다가 공간이 부족해지면 해당 오브젝트가 속한 파일그룹으로부터 추가적인 공간을 할당 받는데 이때, 정해진 익스텐트 크기의 연속된 블록을 할당 받는다.

예를 들어 8KB Page인 상태에서 64KB 단위로 익스텐트를 할당하도록 정의했다면, 공간이 부족할 때마다 파일그룹으로부터 8개의 연속된 페이지를 Heap/Index 구조 오브젝트에 할당해 준다. 익스텐트 내의 페이지는 인접하지만 익스텐트끼리는 인접하지 않는다.

MSSQL8페이지 익스텐트만 사용하고 페이지는 8KB 고정이기 때문에 익스텐트의 크기는 항상 64KB 이다.

 

Heap/Index 구조 오브젝트 (세그먼트)

 테이블, 인덱스처럼 저장 공간을 필요로 하는 데이터베이스 오브젝트

저장 공간을 사용한다는 것은 한 개 이상의 익스텐트를 사용한다는 뜻이다.

 

File Group

 세그먼트를 담는 컨테이너, 여러 데이터 파일로 구성된다.





 

임시 데이터 파일

 대량의 정렬이나 해시 작업 수행 중에 메모리가 부족해지면 중간 결과 집합을 저장하는 용도로 사용한다. MSSQL은 하나의 tempdb 만 사용한다. 전역 리소스이다.


트랜잭션 로그

 버퍼 캐시에 가해지는 모든 변경 사항을 기록하는 파일로 데이터베이스마다 하나씩 생기며 확장자는 .ldf 이다.

Fast Commit 매커니즘[각주:1]을 사용한다. 

내부적으로 '가상 로그 파일' 이라 불리는 더 작은 단위의 세그먼트로 나뉘며, 세그먼트가 너무 많아지지 않도록 (조각화) 옵션을 지정하는게 좋다.

예를 들어, 애초에 넉넉한 크기로 만들어 자동 증가가 발생하지 않도록 하거나, 어쩔 수 없이 자동 증가한다면 증가하는 단위를 크게 지정하는 것이 좋다.







  1. 사용자 처리 내용이 메모리상의 버퍼블록에만 기록된 채 아직 디스크에 기록되지 않더라도 트랜잭션 로그를 믿고 빠르게 커밋을 완료한다. [본문으로]
SQL 2015. 3. 23. 18:08

인덱스 기본 정리

링크 : 

  1. 1. 데이터 파일의 구조
  2. 2. DBMS 메모리 구조
  3. 3. 인덱스 기본
  4. 4. 정렬을 제거하는 방법
  5. 5. 프로시져 캐시 경합 확인하기
  6. 6. Loop 쿼리 제거하기
  7. 7. 튜닝 결과 샘플 보기


아이템 DB의 인덱스와 쿼리를 튜닝해서 실행 비용을 90% 감소 시킨 노하우를 팀과 공유하기 위한 노트


PK 인덱스 특성을 고려한 데이터 베이스 성능 향상 도모

B-트리 구조적 특징에 따라 설계에 반영해야 할 요소를 확인할 필요가 있다.

PK 설계는 데이터를 접근할 때 경로를 제공하는 성능의 측면에서 매우 중요한 의미를 갖는다. 때문에 설계 마지막에 컬럼의 순서를 조정할 필요가 있다.


PK가 복합식별자 일 때

순서 결정 기준 인덱스 정렬 구조를 이해한 상태에서 인덱스를 효율적으로 사용할 수 있도록 PK 순서를 지정한다. 복합 인덱스의 경우 앞쪽에 위치한 값이 최대한 필터링 가능해야 한다.

(점 조건 + 점 조건 + ……+ 선분 조건)



옵티마이저와 실행계획

옵티마이저 - SQL문에 대해 최적의 실행 방법을 결정하는 역할을 한다.

최적의 실행방법을 실행 계획이라고 한다. (Execution Plan)

가장 적은 비용으로 계획을 만들기 위해 노력한다.

관계형 데이터 베이스는 SQL문을 통해서만 데이터를 처리할 수 있는데 사용자의 요구사항만 기술할 뿐 처리 과정에 대한 기술은 하지 않는다. 때문에 요구사항을 만족하는 다양한 실행 방법이 존재할 수 있다.

DB는 옵티마이저가 결정한 실행 방법대로 실행 엔진이 데이터를 처리하여 결과 데이터를 사용자에게 전달할 뿐이다.



MSSQL의 비용기반 옵티마이저 (CBO, Cost Base Optimizer)

규칙 기반 옵티마이저는 호환성을 위해서만 남아있다.


질의 변환기

-        SQL문을 처리하기에 더욱 용이한 형태로 변환한다.

대한 계획 생성기

-       동일한 결과를 생성하는 다양한 대안 계획을 만든다.

-       인덱스 연산의 적용 순서, 연산 방법, 조인 순서 변경

-       대안 계획이 많아지면 최적화 수행 시간이 길어지기 때문에 계획의 수를 제한한다.

-       최적의 계획이 포함되지 않을 수도 있다.

 

비용 예측기

-       계획의 비용을 예측하는 모듈



B-트리 인덱스 구조


링크 화살표 중요

최상단은 Root Block이라 한다.  Branch Block은 분기를 목적으로 하며 하위 블록의 포인터를 갖는다. Leaf Block은 인덱스를 구성하는 컬럼 데이터와 레코드 식별자를 갖는다.

Leaf Block의 양방향 링크는 오름차순, 내림차순 정렬을 쉽게한다.



B-트리 인덱스 검색 방법


1. 37을 검색한다.

2. Root Block 좌측 값보다 작거나 같으면 좌측으로 이동한다.

3. Branch Block 좌측 값보다 작거나 같으면 좌측, 크면 우측, 사이값이면 가운데로 이동한다.

4. Leaf Block 이면 찾으려는 값을 검색한다.

5. Leaf Block에 값이 존재하면 레코드 식별자를 이용하여 해당 테이블을 읽는다.



디스크 I/O 부하

db file sequential read 

 : single block I/O 대기 이벤트, 한번의 I/O call 에 하나의 데이터 블록만 읽는다. 인덱스를 읽을 때, 인덱스를 거쳐 데이블에 액세스 하는 경우 발생

db file scattered read

 : multiblock I/O 대기 이벤트, I/O call 이 필요한 시점에 인접 블록을 같이 읽어 메모리에 적재한다. table full scan or index fast full scan 인 경우 발생







비클러스터형 인덱스 탐색 순서

- 클러스터형 인덱스가 없는 경우 Leaf block으로 이동 후 RID 를 이용해서 데이터 접근

- 클러스터형 인덱스가 있는 경우 Leaf block으로 이동 후 클러스터형 인덱스를 이용해서 데이터 접근


간단히 설명하면 예를 들어 클러스터형 인덱스 컬럼이 A, 비클러스터형 인덱스 컬럼이 B, C 일 때

비클러스터형 인덱스 구성은 B, C, A 이다. 비클러스터형 인덱스 뒤엔 항상 클러스터형 인덱스가 붙는다.





테이블 스캔을 이용할 때는 순서대로 조건에 맞는 데이터를 찾는다.





비클러스터형 인덱스 구성 CN

SELECT 조건절이 CN AND ITemCode 인 경우 데이터 추출 과정

랜덤 액세스가 발생하면서 낭비가 발생




인덱스 수정으로 랜덤 액세스가 제거된 추출 과정



추가로 인덱스 단편화가 심할 때 상대적으로 SELECT 성능이 20% 정도 감소를 보였음








프로그래밍 일반 2014. 12. 23. 16:25

C++ 유닛테스트

게임처럼 요구사항이 수시로 변하는 프로그램에서 유닛테스트를 적용하기란 쉽지가 않다.


개인적으로는 데이터 의존성 테스트나 누락여부를 체크하는 부분에 사용하는게 적당하다고 생각한다.


예로 아이템을 추가할 때 연관 설정 파일들 (사운드, 연출, 스크립트 등..)에 누락이 없는지 확인하는지 테스트


최대한 일반화 할 수 있는 것들을 케이스로 만들어 적용하면 데이터 스크립팅과 관련된 이슈들은 QA에 올린 다음 문제를 발견하고 원인을 파악하는 수고들을 덜 수 있다.


유닛 테스트 익스플로러가 지원되는 컴파일러를 사용 중이라면 문제가 없지만 그렇지 않은 경우 가볍게 사용할 수 있는 테스트 코드를 추가한다.






테스트 결과를 처리하고 수집하는 클래스만 따로 확인하면 아래의 코드를 보면 되고


/**

@brief 테스트의 결과를 처리하고 수집

테스트 객체 트리거

*/

class CTestResult

{

private :

void addSuccess(const CContext& ctxt) { m_success.push_back(ctxt) ; }

void addFailure(const CContext& ctxt) { m_failure.push_back(ctxt) ; }

void addError  (const CContext& ctxt) { m_error.push_back(ctxt)   ; }

public  :

// Helper Method

int totalRunCount() const { return (successCount() + errorCount() + failureCount()) ; }

int successCount()  const { return static_cast<int>(m_success.size()); }

int failureCount()  const { return static_cast<int>(m_failure.size()); }

int errorCount()    const { return static_cast<int>(m_error.size())  ; }


/// 현재 테스트가 모두 성공적이었는지를 체크한다.

bool wasSuccessful() const

{

if ( 0 == errorCount() && 0 == failureCount() )

return true ;

return false ;

}


/// 테스트 결과 초기화

void clear()

{

m_timer.clear()   ;

m_success.clear() ;

m_failure.clear() ;

m_error.clear()   ;

}


/// 테스트 구동 함수. 하나의 메서드에 대해서 구동을 하게 된다.

bool run(const CMethod& method)

{

// 테스트 함수 내부 for구문 처리용

bool bResult = false;

int iIterator = 0;

int iCancelIterator = -1;


m_timer.start() ;


do {


try

{

// 넘겨받은 테스트 함수를 호출한다.

bResult = method.execute(iIterator, iCancelIterator) ; 


CContext c("OK","",method.name(),"",0) ;

addSuccess(c) ;

}

catch(CFailureException& e)

{

addFailure(e.context()) ;

}

catch(std::exception& e)

{

CContext c(e.what(),"",method.name(),"",0) ;

addError(c) ;

}

catch(...)

{

CContext c("unexpected exception is occured","",method.name(),"",0) ;

addError(c) ;

}


iCancelIterator = iIterator;


} while (false == bResult);


m_timer.stop() ;

return true ;

}


/// 파일 포인터에 테스트 결과를 출력해주는 함수

friend inline void WriteResult(const CTestResult& test, FILE* fp);


/// 테스트 결과를 trace하는 함수

friend inline void TraceResult(const CTestResult& test);



private :

CTimer m_timer ; // 시간 측정용 객체


std::vector<CContext> m_success ; // 성공한 테스트 정보 객체

std::vector<CContext> m_failure ; // 실패한 테스트 정보 객체

std::vector<CContext> m_error   ; // 에러 테스트 정보 객체

} ;


유닛 테스트는 보통 테스트 assert를 사용해서 실패하면 결과를 기록하는데 테스트 케이스 구문에서 for 루프를 도는 경우 첫번째 assert 실패 시 해당 케이스는 테스트를 종료하게 된다.

그런 문제를 해결하기 위해 실패한 곳부터 다시 처리하도록 만든 부분이 do {} while 문으로 처리한 구문이다.


테스트 케이스는 CTestCase를 상속 받으면 된다.

아래는 사운드 테스트 케이스 예제..


#pragma once


#include "CppUnitTest.h"


namespace cppunit

{

namespace testcase

{

class CTestCaseDiceSound

: public cppunit::CTestCase

{

public:

CTestCaseDiceSound() {}

CTestCaseDiceSound(const CTestCaseDiceSound& rhs)

: CTestCase(rhs)

{

_append(&CTestCaseDiceSound::TestFuncCheckDiceThrowSound);

_append(&CTestCaseDiceSound::TestFuncCheckDiceSwingSound);

}


virtual CTest* clone() const override

{

return new CTestCaseDiceSound(*this);

}


/// 사운드 매핑 테이블 체크

bool TestFuncCheckDiceThrowSound(int& output_iterator, const int iCancelIterator);


/// 

bool TestFuncCheckDiceSwingSound(int& output_iterator, const int iCancelIterator);

};

}

}


/**

@brief 사운드 매핑 테이블 체크

*/

bool cppunit::testcase::CTestCaseDiceSound::TestFuncCheckDiceSwingSound( int& output_iterator, const int iCancelIterator )

{

output_iterator = 0;


for ( ..;

               ..;

++iter, ++output_iterator)

{

if (output_iterator <= iCancelIterator) continue;

    ... 

    ...

{

const bool bIsFind = strSwing != CString();


char buff[256] = {0,};

sprintf_s(buff, sizeof(buff), "[%d] find error - swing dice sound", pTempItemInfo->m_nItemCode);


// 여기가 중요하다.

// 테스트 assert 사용!!

assertTrue(buff, bIsFind);

}

}


return true;

}



케이스는 테스트 그룹에 포함된다.


/********************************************************************

purpose: 관련 테스트 그룹

*********************************************************************/

#pragma once


#include "CppUnitTest.h"

#include "TestCaseDiceSound.h"


namespace cppunit

{

namespace testgroup

{

class CTestGroupDice

: public CTestSuite

{

public :

CTestGroup_Dice()

{

addTest(std::auto_ptr<CTest>(new testcase::CTestCaseDiceSound));

  addTest(std::auto_ptr<CTest>(new 케이스));

  ..

  .. 

}

};

}

}


테스트를 실행하는 클래스 사용 예

// trace 출력

cppunit::CTestRunner::run(std::auto_ptr<cppunit::CTest>(new cppunit::testgroup::CTestGroupDice));


// 텍스트 출력

COleDateTime currentDate(COleDateTime::GetCurrentTime());

SYSTEMTIME tempTime;

currentDate.GetAsSystemTime(tempTime);


CStringA strFileName;

strFileName.Format("UnitTestResult_%4d%2d%2d_%2d%2d%2d.txt"

, tempTime.wYear

, tempTime.wMonth

, tempTime.wDay

, tempTime.wHour

, tempTime.wMinute

, tempTime.wSecond);


FILE* fp = fopen(strFileName.GetString(), "ab");

cppunit::CTestRunner::run(std::auto_ptr<cppunit::CTest>(new cppunit::testgroup::CTestGroupDice), fp);

fclose(fp);


테스트 결과 출력 예



프로그래밍 일반 2014. 10. 24. 11:28

통합 빌드 시스템(Unity Build) 허드슨에 구축하기

예전에 통합 빌드 시스템(Unity Build)에 관한 이야기를 한적이 있었다.


링크 : 통합 빌드 시스템(Unity Build)


이번에는 통합 빌드 시스템을 허드슨 msbuild 에서 사용하는 방법에 대한 이야기해보려고 한다.


Pre-Build Event 명령어

$(SolutionDir)externals\UnityMaker\UnityMaker.exe $(ProjectPath) IndonesiaRelease UnityBuild 2


우선 빌드전 이벤트 명령어가 달라졌는데 개발 환경에 따라 유연하게 대처하기 위한 매개변수들이 늘어났을 뿐이다.


로컬에서 개발하는 경우에는 해당 명령어가 잘 동작하지만 허드슨에서는 빌드 환경 설정에 따라 문제가 발생할 수도 있는데


허드슨 MSBuild 를 사용하는 경우 VS와 매크로가 다르기 때문이다.


로컬 빌드에서는 VS Pre-Build Event 에 실행 명령어를 작업하면 되고 라이브 배포용 빌드에서는 해당 이벤트 명령어를 빌드에서 제외 시킨다.





허드슨 빌드 환경에서 프로젝트 빌드 전에 Execute windows batch command를 하나 추가한다.


build step를 하나 추가하고 'Build a Visual Studio project or solution using MSBuild' 위로 추가한 build step을 드래그해서 올린다.


@rem UnityMaker 실행

SET SOLUTION_DIR=%CD%\

SET PROJECT_XML=%CD%\src\Client.vcproj

%SOLUTION_DIR%externals\UnityMaker\UnityMaker.exe %PROJECT_XML% %_JOBS_PROJECTBUILD% UnityBuild 2

SET ERR_LEVEL=%errorlevel%


@rem UnityMaker.exe 처리 성공한다면 .vcproj 변경 커밋

IF "%ERR_LEVEL%"=="0" (

 svn commit %PROJECT_XML% -m "hudson unity build"

)


exit /b %ERR_LEVEL%


build step에 로컬빌드와 같은 의미의 명령어를 환경에 맞도록 작성해주면 허드슨 셋팅은 끝이다.


이 단계에서 고려할 사항이 혹시라도 기존에 작성한 통합 빌드 시스템 구축하기에 첨부한 소스를 참고해서 Unitymaker를 만들었다면 허드슨 빌드 환경에서 빌드에 실패할 것이다.


그 이유는 UnityMaker의 리턴 값 때문인데..기본적으로 명령 프롬프트 상에서 어떤 프로그램이나 명령어를 실행시킬 때, 프로그램이 성공적으로 끝나면 정수 0(제로)을 OS에 반환하며 끝낸다는 룰 때문이다. 


이것을 Error Level 이라고 하는데 %ERRORLEVEL% 이라는 환경 변수 안에 최근에 종료된 프로그램/명령어가 돌려준 값이 들어 있다.


BuildLog.htm 파일을 확인하면 아래와 같다.





이런 문제는 UnityMaker 프로그램이 정상적으로 종료될 때 0을 리턴하도록 수정하고 실패 리턴값을 세분화하면 된다.





프로그래밍 일반 2014. 10. 22. 18:28

enum 범위 한정 기법과 보다 안전한 문자열 표현

C++ 11 이전 버전의 열거형은 허용범위 밖에서 열거형 이름을 사용할 수 있기 때문에 이름 충돌 문제가 발생한다.


참고 : C++ 11 enum 스펙


일반적으로는 문제가 되지 않지만 통합 빌드(Unity Build) 환경에서는 골치 아픈 문제이기도 하다.


이런 문제는 namespace를 이용해서 피해갈 수 있다.


namespace EType

{

           enum Type

           {

               Null

               , Guerrilla

               , Always

               , MiniDuration

               , CubePiece

               , Discount   

           };

}

범위를 설정하고 내부적으로 열거형의 이름은 Type을 사용한다.


이렇게 사용하면 이름 중복을 피하기 위해 무리해서 이름을 작성할 필요도 없어진다.


eType_Guerrilla

, eType_Always


이름충돌을 피하기 위한 구분자_사용하는 이름, 

.....


만약 클래스 내부에서 사용하는 열거형인 경우에는 

struct EType

{

           enum Type

           {

               Null

               , Guerrilla

               , Always

               , MiniDuration

               , Max

           };

};

이런 식으로 열거형의 범위를 한정할 수가 있다.


위에서 설명한 기법들은 언리얼 엔진 프로그래밍 가이드에 나와있는 old style 표기법이다. 만약 컴파일러가 지원한다 enum class를 사용하도록 한다.


여기에 추가로 열거형을 스트링으로 표현하고 싶을 때 어떻게 하면 좋은지 설명하도록 하겠다.


일반적으로 열거형을 선언한 후 코드 어딘가에 스트링 표현식을 작성할 것이다.


... 코드 어딘가 열거형 스트링

TCHAR* szTypeString[] = {

 _T("Null")

, _T("Guerrilla")

, _T("Always")

, _T("MiniDuration")

, _T("Max")

};

 

... 코드 어딘가에서 출력

for (int i = EType::Null; i < EType::Max; ++i)

{

           출력함수(szTypeString[i]);

}

이런 방식은 열거형이 변경될 때마다 항상 스트링도 수정해야하는데 최악의 경우 프로그램을 죽이는 버그를 만들 수도 있다.


컴파일러는 위의 위험 사항을 알려주지 않는다.


디파인 함수를 이용해서 스트링으로 바꾸는 기법은 예전에 한번 설명한 적이 있는데


참고 : 열거형 스트링 표현


아래와 같은 코드는 열거형의 범위를 한정하고 문자열로 표현하는데 보다 안전하다.


#if !defined(DO_TEXT)

#define DO_TEXT(e)    L#e,

#endif


#if !defined(DO_ENUM)

#define DO_ENUM(e)    e,

#endif


namespace EType

{

#define DEF_EType(NAME) \

                     NAME(Null) \

                     NAME(Guerrilla) \

                     NAME(Always) \

                     NAME(MiniDuration) \

                     NAME(CubePiece) \

                     NAME(Discount)

 

           enum Type

           {

                     DEF_EType(DO_ENUM)

           };

 

           inline const TCHAR* GetConvertSring(EType::Type type)

           {

                    const TCHAR* szString[] = {

                                DEF_EType(DO_TEXT)

                     };

 

                     return szString[type];

           }

}


담당 프로젝트 2014. 10. 22. 18:16

모두의 마블 해외버전




2012년 10월 15일~ 2015년 5월 29일 모두의 마블 PC 해외버전 개발팀

사용 언어 및 라이브러리 : C++, STL, MFC, Direct Draw, TCP/IP, SQL

개발 툴 : VS2008, MSSQL 2008, SVN

간략한 업무 내용

- 컨텐츠에 대한 클라이언트, 서버, DB 전반에 걸친 설계 및 구현

- 지속적인 코드 리팩토링 & 일관성 유지 관리

- 테이블, 인덱스, 쿼리 퍼포먼스 튜닝 (기존 대비 처리 비용 90% 감소)

- MSSQL을 이용한 DB 운영

- C++ 유닛 테스트 구현 및 적용

- 툴 개발 및 기능 개선

- Unity Build 시스템 개발 및 허드슨 연동 작업

- 개발 편의 배치 파일 작업

- 인증 & 빌링 작업

- 5개 국가 서비스 오픈 (2개국 전담, 3개국 지원)




개발 컨텐츠 목록

- 상점 구현 (클라/서버/DB)

- 데이터 드리븐 방식으로 아이템 관리 시스템 변경 (클라/서버)

- 아이템 인벤토리 작업 (클라)

- 마블 충전 (클라/서버/DB)

- 튜토리얼 시스템 (클라/서버/DB) 

- GM 커맨드 기능 추가 & GM 목록 등록 및 관리 시스템 (클라/서버)

- 이벤트 시스템 (클라/서버)

- 해외 오픈 작업 (인증 & 빌링 모듈 작업, DB 운영 지원)

- 채널별 접속자 통계 작업 (서버/DB)

- 대만 오픈 이벤트 작업

- 일본 웹버전 작업, 일본 서비스 오픈 작업

- 이모티콘 시스템 (클라)

- 연승 & 연패 (서버/DB)

- 빙고 시스템 (클라/서버/DB)

- 실시간 등록 및 변경 가능한 이벤트 시스템 (클라/서버/DB)

- 실시간 변경 가능한 토너먼트 시스템 (클라/서버/DB)

- UI 툴 기능 개선, 데이터 Export, Import,  소스 코드 자동 생성 시스템

- 어드벤처 맵 국내 & 해외 버전 개발 (클라/서버)

- Unity Build 작업 환경 구축 (Unity Build 툴/허드슨 연동)

- C++ 유닛 테스트 코드 작성 (클라/서버)

- 실시간 할인 이벤트 시스템 (클라/서버/DB)

- TCP/IP 소켓 통신 빌링 시스템 구현 (서버)

- 캐쉬와 포인트를 이용해서 아이템을 구매하는 시스템 구축 (클라/서버/DB)

- 체험판 시스템 (클라/서버/DB)

담당 프로젝트 2014. 10. 22. 18:16

Legend of EDDA


http://edda.nexon.co.jp/







2010년 2월 1일 ~ 2012년 9월 28일 이야소프트 에다전설 컨텐츠 개발

사용 언어 및 라이브러리 : C++, SQL, DirectX9

개발툴: VS2005, MSSQL 2005, SVN


간략한 업무 내용

- 게임 컨텐츠에 대한 클라이언트, 서버, DB 전반에 걸친 설계 및 구현


개발 컨텐츠 목록

- 단축키 설정 시스템 (클라/서버/DB)

 * 와우에서 동작하는 단축키 시스템과 같음

- 길드 시스템 (클라/서버/DB) 

 * MMO 길드 시스템 포함, 길드 레벨, 길드 스킬 등 

- 친구 관리 시스템 (클라/서버)

- 친구 채팅 메신저 (클라/서버)

    * 친구와 별도로 대화방을 만들어 대화하는 시스템

- 채팅 시스템 (클라)

    * 와우 형식의 채팅 인터페이스 구현

    * 채팅 창마다 메시지 출력 옵션을 설정할 수 있도록 기능 추가

- 조합형 업적 & 호칭 시스템 (클라/서버/DB)

    * 스크립트로 업적을 조합할 수 있도록 작업

    * 예 - 몬스터 인덱스, 죽지 않고, 제한 시간, 사냥 횟수 등을 

    스크립트로 조합하면 "특정 몬스터를 죽지 않고 제한 시간 안에

    몇 번 사냥한다"라는 업적이 만들어지는 구조

- 팝업 형식의 도움말 시스템 작업 (클라/서버) 

    * 특별한 아이템 습득, 강화성공, 몬스터 조우 등 설정한 이벤트 발생시에

       디아블로3의 화면 우측하단 새 이야기처럼 메시지를 전달하는 시스템

    * 스크립트로 수집 이벤트, 전달 범위 등 설정 가능

- 차단 목록 시스템 (클라/서버/DB)

- 뷰티샵 시스템 (클라/서버/DB)

    * 아이템을 이용해서 외형을 바꾸는 시스템

- 이모티콘 시스템 (클라/서버)

    * 유저의 대화 내용을 분석해서 설정된 키워드를 포함하고 있으면

    캐릭터 얼굴표정을 바꾸고 머리 위에 이모티콘을 출력하는 시스템

- 오늘의 기분 시스템 (클라/서버/DB)

    * 사용자가 오늘의 기분을 선택하면 대화면 옆에 이모티콘을 출력

- 다른 사용자에게 장비 노출 옵션 (클라/서버/DB)

- 성물전 참여시 클라이언트 부하를 줄이기 위한 캐릭터 복장 & 이펙트 간소화 처리 (클라)

- 트리 인터페이스 구현 (클라)

- 채팅 창이나 UI에서 텍스트 개행 처리를 할 때 단어가 잘려서 개행되지 않도록 처리 (클라)

- 개인 보관함 창고 (클라/서버/DB)

- 리뉴얼전 튜토리얼 시스템 (클라/서버/DB)

- 캐릭터 생성 씬 & 외형 변경 시스템 (클라/서버/DB)

- 각종 유로 아이템 작업

 


담당 프로젝트 2014. 10. 22. 18:16

풍림화산


엠게임 근무기간 : 2008년 12월 22일 ~ 2010년 1월 31

사용 언어 및 라이브러리 : C++, STL, DirectX9

개발 툴: VS2005, SVN

담당 업무 : 3D 클라이언트 개발

-      분할 팩킹 관리 시스템 개발, 팩킹 툴 개발

해쉬 알고리즘을 이용, 적정 사이즈로 데이터 크기를 분할해서 팩킹

-      클라이언트 memory leak & 각종 버그 수정

-      클라이언트 프로파일링 시스템 개발

-      플래시 메신저 프로그램 개발

-      런처 리뉴얼

-      UI 개발 툴 & 게임 개발 툴 각종 버그 수정

Memory leak, 종료 시 다운 문제 등..

-      라이브콘텐츠 개발

-      겨울 업데이트 중 하나인 환영의 숲 콘텐츠 개발

필드 몬스터가 맵에 접속한 유저의 외형과 능력을 복사해서 변신


http://wffm.mgame.com/news/news.mgame?rtype=N&message_id=2599