태그 미디어로그 위치로그
'개발자 세상'에 해당되는 글 25건
DBMS 벤치마크와 벤치마크 전쟁
개발자 세상

우리는 벤치마크라는 말을 자주 듣고, 자주 사용합니다. 또한 데이터베이스과 관련이 없으신 분들도 IT 업종에 종사하신다면, 한번은 TPS 라는 단어를 들어 보셨을 것입니다. TPS는 Transaction Per Second의 약자로 초당 처리할 수 있는 트랜잭션의 수를 의미합니다. 더불어 TPC 벤치마크에 대해서도 들어보셨을 것입니다. (TPC-A, TPC-B, TPC-C, TPC-H, 등..)

인터넷에 Jim Gray의 Database Benchmark Handbook [1]이라는 자료가 있습니다. 책으로 된 자료도 있는데, 데이터베이스 벤치마크의 전반적인 흐름에 대해 이해할 수 있는 자료입니다.

오늘은 이 자료 및 다른 관련자료를 인용하여 DBMS 벤치마크에 대한 재밌는 이야기를 간단히 적고자 합니다.

이런 마크 많이 보셨죠?



자동차나 전자제품을 구입하면 에너지효율에 대한 등급이 적힌 스티커가 붙어있습니다. 이게 벤치마크의 대표적인 예입니다. 소비자가 제품을 직접 구매하지 않고도 제품의 특징을 알 수 있는 객관적인 수치를 제공하고자 하는 것입니다.

자, 이제 컴퓨터 시스템을 구입하시려고 합니다. 어떻게 결정하시나요? 아마도 벤치마크 결과를 참고하시겠지요? 벤치마크의 대해 Jim Gray는 [1]에서 다음과 같이 이야기 합니다.

각각의 벤치마크는 다음의 질문에 답하려 한다. “어떤 컴퓨터를 사야하는가?”. 그 답은 분명히 “원하는 작업을 수행하면서 비용이 최소인 시스템”일 것이다. 비용(cost-of-ownership)이란 프로젝트 리스크, 개발 비용, 운영 비용, 하드웨어 비용과 소프트웨어 비용을 포함한다. 프로젝트 리스크나 개발비용, 운영 비용 등은 측정하기 어렵다. 반면, 컴퓨터 성능은 측정하고 비교할 수 있다.

한번 이런 질문을 던져 보겠습니다. “벤치마크 결과를 어느 정도 신뢰하십니까?”

전문가들이 벤치마크 결과에 대해 흔히 말하는 재밌는 한마디는 소개하면 다음과 같습니다.

“업체가 발표한 벤치마크 결과는 그 제품의 성능이 절대로 그 결과는 넘지 않을 것을 보장한다.”

저는 이 이야기를 처음 들었을 때 한참을 웃었습니다. 참으로 예리한 표현입니다. ^^

다시 말해 벤치마크 결과를 비교용으로 활요하지 절대로 있는 그대로 믿지는 말라고도 이야기 합니다.

데이터베이스 성능이라는 것이 어떠한 데이터를 가지고, 어떠한 환경에서, 어떻게 측정했는지에 따라 천차만별입니다. 하드웨어, 운영체제, 네트워크등 많은 요인에 의해 달라질 수 있기 때문에 실제 환경에서 직접 돌려보기 전에는 성능을 미리 알기란 어려운게 사실입니다.

그렇다고 제품을 사기도 전에 실제 환경에서 운영해 보는 것은 쉽지 않습니다. 설령 DBMS 업체가 평가 버전을 제공한다고 하여도, DBMS의 선정은 응용 프로그램 개발과 완전히 독립적일 수 없기 때문입니다. 실제 환경에서 성능을 비교하기 위해서 응용 프로그램의 DBMS 의존적인 부분을 맞춰주어야 하는데, 이 자체가 추가의 개발 비용이지요.

이러한 비용까지 감수하며 제품을 선택할지는 고객의 몫인데, 가끔 업체가 이를 부담해야 해야하는 웃지 못한 일도 일어나는게 현실인 것 같습니다.

‘위스콘신 벤치마크’ 어디서 많이 들어보시지 않으셨나요? 1980년대 초 위스콘신의 David DeWitt교수가 고안한 벤치마크인데, 이 벤치마크가 소위 ‘벤치마크 전쟁’의 기폭제가 되었다고 합니다.

벤치마크에 전쟁이라 표현하는 이유는 다음과 같은 일이 일어났기 때문입니다. 벤치마크에서 진 업체는 실험이 잘 못 되었거나 벤치마크 자체가 의미가 없다고 주장했습니다. 또한 전문가를 동원하여 벤치마크에서 이기기 위해 다시 수행하고, 또 진 업체가 다시 하기를 반복하였습니다. 혹은 벤치마크를 이기기 위한 기능을 포함한 특별 버전을 이용하여 실험을 하고 이 기능이 다음 릴리즈에 포함될 것으로 약속하는 식으로도 진행되었습니다.

심지어 위스콘신 벤치마크가 발표된 이후에 오라클 CEO Larry Ellison이 벤치마크 결과에 매우 화가나서 위스콘신 대학의 학과장을 찾아가 David DeWitt 교수를 해고하도록 하려 했다는 일화가 있습니다. David DeWitt 교수의 2001년 인터뷰[2]에서 이 일을 이야기하며 다음과 같이 이야기 합니다.

DeWitt: “그는 테뉴어(교수의 정년보장)의 개념 – 내가 오라클에 대해 그다지 긍정적이지 않은 이야기를 했다고 해서 학과장이 날 해고하지 않는다는 것 – 을 이해하지 못하고 있었습니다.”
Interviewer: 교수들에게 테뉴어를 받기 전까지 벤치마크 작업을 하지 않을 것을 조언합니까?
DeWitt: (웃으며) 예, 확실히 그렇게 조언합니다.

이런 벤치마크 전쟁을 통해 벤치마크도 발전하고 DBMS도 발전했습니다. 결과적으로 1988년에 34개 소프트웨어 및 하드웨어 업체가 Transaction Processing Performance Council (TPC)을 공동으로 만들었고, 이후 여기서 만들어진 TPC-A, TPC-B 벤치마크(주1) 덕분에 벤치마크 전쟁이 급격이 감소했다고 합니다.

벤치마크가 신뢰할 만한 것이 되려면 어떻게 해야할까요?

일단 하나의 벤치마크로 DBMS를 평가할 수 없습니다. OLAP환경과 OLTP환경에서 필요로 하는 성능이 같을 수 없습니다. 따라서 특정한 도메인에 맞는 벤치마크를 개발하는 것이 더 합리적입니다.

Database Benchmark Handbook[1]에서 Jim Gray는 특정 도메인의 벤치마크가 아래의 4가지 기준을 만족해야 한다고 이야기합니다.

  • 적절성(Relevant): 해당 도메인의 대표적인 연산을 수행하며 최대 성능과 성능 대비 가격을 측정해야 한다.
  • 이식성(Portable): 서로 다른 시스템과 구조에 쉽게 벤치마크를 구현할 수 있어야 한다.
  • 확장성(Scalable): 벤치마크가 작은 시스템에서 큰 시스템까지 적용할 수 있어야 한다. 이는 더 큰 시스템과 병렬 시스템에 확장할 수 있어야 한다.
  • 단순함(Simple): 벤치마크는 이해할 수 있어야 한다. 그렇지 않으면 신빙성이 떨어질 것이다.

DBMS 혹은 다른 컴퓨터 시스템에서 많은 변수를 세밀하조 조절해가며 성능을 측정해보신 분은 성능이 얼마나 많은 변수로 인해 크게 달라질 수 있는지 아실 것 입니다. 따라서 벤치마크에 이러한 고려를 포함하고, 적절하면서도 이식성에 확장성도 있고, 단순하기까지한 벤치마크를 만드는 일이 얼마나 힘든일인지 상상하실 수 있을 것입니다. 그래서 인지 TPC-C 벤치마크의 스펙을 설명한 문서[4]가 130페이지에 달합니다.

저도 아직 이 문서를 완전히 읽어본 적이 없습니다. 아마 TPC-C 벤치마크의 요구사항을 만족하며 벤치마크를 구현하려면 이 내용을 제대로 파악하고 있어야 할 것 같네요. 직접 벤치마크를 개발하실 분이 아니고, 벤치마크가 어떤 테스트인지 대충 파악하고 싶으시다면 [5]을 읽어 보시길 추천합니다. 관심이 있으신 분은 TPC 홈페이지에서 다른 TPC 벤치마크에 대해서도 더 알아보실 수 있습니다.

참고로 Database Benchmark Handbook [1]를 보시면 TPC  이외에도 다른 벤치마크들이 소개되어 있으니 관심이 있으신 분은 참고하세요.

주석

(주1) 2009년 4월 현재, TPC를 참고하면, TPC-A, TPC-B를 비롯하여, TPC-D, TPC-R, TPC-W는 사용되지 않는다고 되어 있습니다. 현재 의미있는 TPC 벤치마크는 TPC-App, TPC-C, TPC-E, TPC-H이며, 각각에 대한 소개는 여기에서 확인하실 수 있습니다.

참고자료:

[1] Database Handbook:
http://research.microsoft.com/en-us/um/people/gray/benchmarkhandbook/toc.htm

[2] David DeWitt 2001년 인터뷰:
http://www.sigmod.org/interviews/mp3s/dewitt.mp3
http://www.sigmod.org/sigmod/record/issues/0206/dewitt-interview.pdf

[3] TPC-Benchmark 소개:
http://tpc.org/information/benchmarks.asp

[4] TPC-C 벤치마크 스펙:
http://tpc.org/tpcc/spec/tpcc_current.pdf

[5] TPC-C 벤치마크 개요:
http://tpc.org/information/sessions/sigmod/indexc.htm

덧붙이는 말: 필명을 flow에서 phlow로 바꾸었습니다.

Linux Direct IO의 이해 (Synchronous IO와의 차이를 기반으로)
개발자 세상

안녕하세요. 알티스토리에 처음으로 글을 남기에 되었네요.

저는 알티베이스에서 개발자로 일하고 있습니다. 알티스토리에 ‘flow’라는 필명으로 글을 올릴 예정입니다. 글에 오류가 있거나, 이해가 안가시는 부분이 있으면 자유롭게 코멘트에 남겨주시면 감사하겠습니다.

리눅스에서 Direct IO와 Synchronous IO를 정확하게 이해할 수 있는 재밌는(?) 실험을 했던 것이 기억나 블로그를 통해 공유할까 합니다. 먼저 간단히 정리하면 Direct IO는 IO가 버퍼를 거치느냐 여부를 결정하는 정책을 의미하고, Synchronous IO는 IO가 즉시 반영되는지 여부를 결정하는 정책을 의미합니다. 둘은 분명히 다르지만, 운영체제에서 Synchronous IO를 지원하는 것 자체가 버퍼가 있다는 가정으로 특정한 경우 이를 조작하기 위한 것이므로 Direct IO는 당연히 Synchronous IO 입니다. 이 부분이 조금 헷갈릴 수 있는데, 아래의 실험을 살펴보시면 도움이 되리라 생각합니다.

자, 시작합니다!

Direct IO란 무엇인가요? Direct IO란 운영체제의 버퍼를 거치지 않고(bypass), 직접 IO를 수행하는 것입니다. 혹자는 파일 시스템 버퍼라도고 하는데, 저는 그냥 운영체제가 관리하는 버퍼라고 하겠습니다. Direct IO와 반대되는 용어를 굳이 정의한다면, 반대로 일반적인 IO를 Buffered IO라 할 수 있습니다. (참고로 fopen()을 이용하여 파일을 여는 경우, 응용프로그램 단계에서 IO를 버퍼링하기도 합니다. 이 이슈는 이 글에서 다루지 않습니다.)

동기화의 관점에서 IO에는 Synchronous IO (동기 IO)와 Asynchronous IO (비동기 IO)가 있습니다. 이는 쓰기에 대한 IO를 수행할때 IO가 발생한 즉시 디스크에 반영하느냐 아니냐의 정책을 결정하는 것입니다.

자 이제 프로그래머의 관점에서 생각해봅시다. 위에서 설명한 IO 정책을 내가 짠 프로그램에서 반영하려면 어떻게 해야 할까요? IO를 수행하려면 가장 먼저 open() 시스템 콜을 호출해야 합니다. 추가로 원하는 정책을 적용시키려면 적절한 플래그를 지정하면 됩니다.

Synchronous IO는 어떻게 수행할 수 있나요? 동기화에 관련된 플래그를 찾아보면 O_DSYNC,O_RSYNCO_SYNC가 나오고, 이것들은 POSIX.1 표준입니다. 간단히 이 플래그를 open()의 플래그에 적용하시면 됩니다. (혹은 fsync() 등으로도 가능합니다.)

Direct IO는 어떻게 수행할 수 있나요? (제가 알기로) POSIX 표준은 Direct IO에 대한 플래그는 정의하지 않고 있습니다. 다행히(?) 리눅스는 (제 기억으로) 2.4 커널 일부에서 O_DIRECT라는 플래그를 제공합니다. (아마 2.6 커널에서는 모두 지원이 될 것 입니다.)

자! 그럼 이러한 플래그가 실제로 어떻게 반영되는지 눈으로 확인할 수 있는 방법은 없을까요?

만약 리눅스를 사용하신다면?

blktrace라는 툴이 있습니다.

간략하게 blktrace에 대해 설명해드리면, 리눅스의 요청 큐(request queue)의 연산을 관찰할 수 있도록 하는 툴입니다.

이제 리눅스에 ‘blktrace’를 이용하면 Direct IO를 Synchronous IO의 동작(behavior)를 관찰할 수 있습니다. (참고로 blktrace은 낮은 버전의 커널 패치가 필요한데, 제가 실험했던 2.6.22 커널에서는 별다른 패치 없이 패키지 설치만을 통해 가능했습니다. 2.6.18이상이라면 별도의 커널패치가 필요 없습니다.)

일반적으로 배포판에서 제공하는 패키지로 설치할 수도 있고, 아래의 주소의 저장소에서도 구할 수 있습니다.

git://git.kernel.org/pub/scm/linux/kernel/git/axboe/blktrace.git bt

blktrace를 이용하여 아래와 같이 완료(complete)된 요청에 대한 결과만을 출력하여 IO가 실제로 발생하는 상황을 살펴볼 수 있습니다. (실제로 더 많은 상태에 대한 정보들이 있는데, 이 실험에서는 실제로 장치에 IO가 요청되었는지만 관심이 있으므로 아래와 같은 옵션으로 필터링한 것 입니다.)

blktrace [블록장치경로] -a complete -o - | blkparse -f “%d,%S,%n\n” -i -

주의: 이 실험을 재현하는 경우 쓰기연산을 수행하는 디스크 혹은 파티션은 운영체제가 설치된 시스템과 같이 중요한 데이터를 포함하지 않아야 합니다. 이 부분을 제대로 이해하지 못하고 실험을 재현하다 생기는 문제는 책임지지 않습니다. 매우 유용한 방법이 있는데, 하드디스크에 적당한 파티션이 없는 분은 사용하지 않는 (각종 행사에서 하나씩은 얻으셨을) ‘USB 드라이브’를 이용하시면 좋습니다.

실험 순서는 아래와 같습니다.

  1. blktrace 실행
  2. IO 발생
  3. blktrace를 이용하여 실시간으로 결과 관찰

기본 옵션 (버퍼링 + 지연된 쓰기 IO 발생)

아래의 소스코드를 컴파일하여 수행합니다.

  1. /* default.c */  
  2. #define _GNU_SOURCE  // O_DIRECT  
  3. #define IOUNIT 128*1024  
  4.   
  5. #include  
  6. #include  
  7. #include  
  8. #include   
  9.   
  10. int main()  
  11. {  
  12.   char *nBuf;                // not aligned buffer  
  13.   char *aBuf;                // aligned buffer  
  14.   int   fd;                  // file descriptor  
  15.   
  16.   // 1MB buffer (not aligned yet)  
  17.   nBuf = (char*) malloc(IOUNIT+getpagesize());  
  18.   // aligned buffer 'aBuf'  
  19.   aBuf = (char*)((unsigned)(nBuf+getpagesize()-1)/getpagesize()*getpagesize());  
  20.   
  21.   printf("OPEN FD: %d\n", fd=open("[블록장치경로]", O_RDWR ) );  
  22.   printf("WRITE: %d\n", write( fd, aBuf, IOUNIT ) );  
  23.   printf("READ: %d\n", read( fd, aBuf, IOUNIT ) );  
  24.   close(fd);  
  25.   
  26.   free(nBuf);  
  27. }  

위의 프로그램은 오픈한 블록 장치(block device)의 0번 주소에 대해 128KB 단위의 쓰기 연산을 한번 수행하고, 그 다음 128KB위치의 해당하는 주소에 128KB크기의 읽기를 한번 수행하는 아주 간단한 프로그램입니다.

위의 프로그램 수행 직 후 blktrace를 이용하여 관찰된 IO는 아래와 같습니다.
R,256,256

분명 쓰기 연산도 수행했는데, 반영되지 않고 있습니다. !!!

몇 초가 흐르면 아래의 IO가 발생합니다.
W,0,256

첫 번째 IO는 쓰기 요청임에도 실제로 READ 요청이 먼저 발생하였습니다. 실제로는 “W,0,256“이 발생하여야 하지만, 일단 버퍼에 요청된 쓰기를 수행하고, 디스크에는 반영하지 않았음을 의미합니다. 실제 IO는 몇 초가 지난 후에야 비로소 발생합니다.

만약 실제 쓰기에 대한 IO가 발생하기 이전에 컴퓨터가 비정상적으로 전원이 나간다거나 하는 상황이 발생하면 어떻할까요? 이러한 이유로 데이터베이스의 트랜잭션 로그의 경우, 위와 같은 코드르 사용하면 안됩니다.

추가로 프로그램을 반복하여 수행하여 봅시다. 실제로 IO가 요청되지 않습니다. 프로그램에게 버퍼에 저장되어 있는 값을 전해주기 때문입니다.

O_SYNC (쓰기 IO 즉시 발생)

처음 작성한 default.c를 아래의 osync.c와 같이 수정합니다. 실제로 다른 부분은 모두 같고,O_SYNC 플래그만이 추가된 것입니다.

  1. /* osync.c */  
  2.   
  3. #define _GNU_SOURCE  // O_DIRECT  
  4. #define IOUNIT 128*1024  
  5.   
  6. #include  
  7. #include  
  8. #include  
  9. #include   
  10.   
  11. int main()  
  12. {  
  13.   char *nBuf;                // not aligned buffer  
  14.   char *aBuf;                // aligned buffer  
  15.   int   fd;                  // file descriptor  
  16.   
  17.   // 1MB buffer (not aligned yet)  
  18.   nBuf = (char*) malloc(IOUNIT+getpagesize());  
  19.   // aligned buffer 'aBuf'  
  20.   aBuf = (char*)((unsigned)(nBuf+getpagesize()-1)/getpagesize()*getpagesize());  
  21.   
  22.   printf("OPEN FD: %d\n", fd=open("[블록장치경로]", O_RDWR | O_SYNC ) );  
  23.   printf("WRITE: %d\n", write( fd, aBuf, IOUNIT ) );  
  24.   printf("READ: %d\n", read( fd, aBuf, IOUNIT ) );  
  25.   close(fd);  
  26.   
  27.   free(nBuf);  
  28. }  

같은 방법으로 blktrace를 시작하고, 위의 프로그램을 수행하면 아래와 같은 결과를 확인하실 수 있습니다.

W,0,256
R,256,256

위의 프로그램을 반복하면, 읽기는 발생하지 않고 쓰기만 계속 발생합니다. 쓰기 연산에 대한 동기화 플래그가 O_SYNC로 설정되어 있기 때문입니다. 참고로 O_SYNC 플래그를 이용하여 파일을 열지 않더라도 fsync()를 이용하여 쓰기 연산을 장치에 실제로 반영하도록 할 수 도 있습니다.

O_DIRECT (버퍼링 하지 않음)

default.c 파일을 아래의 odirect.c와 같이 수정합니다. (O_DIRECT 플래그 사용)

  1. /* odirect.c */  
  2. #define _GNU_SOURCE  // O_DIRECT  
  3. #define IOUNIT 128*1024  
  4.   
  5. #include  
  6. #include  
  7. #include  
  8. #include   
  9.   
  10. int main()  
  11. {  
  12.   char *nBuf;                // not aligned buffer  
  13.   char *aBuf;                // aligned buffer  
  14.   int   fd;                  // file descriptor  
  15.   
  16.   // 1MB buffer (not aligned yet)  
  17.   nBuf = (char*) malloc(IOUNIT+getpagesize());  
  18.   // aligned buffer 'aBuf'  
  19.   aBuf = (char*)((unsigned)(nBuf+getpagesize()-1)/getpagesize()*getpagesize());  
  20.   
  21.   printf("OPEN FD: %d\n", fd=open("[블록장치경로]", O_RDWR | O_DIRECT ) );  
  22.   printf("WRITE: %d\n", write( fd, aBuf, IOUNIT ) );  
  23.   printf("READ: %d\n", read( fd, aBuf, IOUNIT ) );  
  24.   close(fd);  
  25.   
  26.   free(nBuf);  
  27. }  

blktrace를 다시 시작하고, 위의 프로그램을 실행하면 아래의 결과를 얻을 수 있습니다.

W,0,256
R,256,256

버퍼링 효과가 적용되지 않아 즉각적으로 읽기와 쓰기가 발생하였습니다. 프로그램을 반복하여 수행하여도 정직하게 반복합니다.

버퍼링과 prefetch의 관계

버퍼링은 단순히 IO가 반복하여 발생하는 경우에만 적용되는 것은 아닙니다. 위의 경우와 같이 한번 IO와 발생한 주소에서 다시 발생할 확률이 높다고 가정하는 것을 “temporal locality”라고 합니다. 하지만 데이터를 연속적으로 저장되고 처리될 확률이 높기 때문에 A라는 주소에서 IO가 발생했다면, A+1주소에서 짧은 시간안에 다시 IO가 발생할 확률 역시 높습니다. 이러한 것은 “spatial locality”라고 합니다.

Spatial locality를 이용하여 성능을 높이기 위한 대표적인 방법은 IO의 단위를 크게 하는 것입니다. 사용자가 1바이트를 읽어도 기본 IO의 단위가 8KB라면, 이후 주소에 대한 데이터까지 상위 단계의 메모리로 함께 복사해오겠죠. 이에 더불어 prefetch라는 기술을 이용할 수도 있습니다. 단순히 A주소에 대한 IO가 발생하면 A+1주소에 대한 IO까지 미리 수행하여 버퍼에 저장하는 것입니다.

Prefetch효과를 확인하기 위해, 아래의 default_prefetch.c 파일을 생성합니다. default.c파일에서 쓰기 연산을 제거하고 읽기만 수행한 것입니다.

  1. /* default_prefetch.c */  
  2. #define _GNU_SOURCE  // O_DIRECT  
  3. #define IOUNIT 128*1024  
  4.   
  5. #include  
  6. #include  
  7. #include  
  8. #include   
  9.   
  10. int main()  
  11. {  
  12.   char *nBuf;                // not aligned buffer  
  13.   char *aBuf;                // aligned buffer  
  14.   int   fd;                  // file descriptor  
  15.   
  16.   // 1MB buffer (not aligned yet)  
  17.   nBuf = (char*) malloc(IOUNIT+getpagesize());  
  18.   // aligned buffer 'aBuf'  
  19.   aBuf = (char*)((unsigned)(nBuf+getpagesize()-1)/getpagesize()*getpagesize());  
  20.   
  21.   printf("OPEN FD: %d\n", fd=open("[블록장치경로]", O_RDWR ) );  
  22.   printf("READ: %d\n", read( fd, aBuf, IOUNIT ) );  
  23.   close(fd);  
  24.   
  25.   free(nBuf);  
  26. }  

blktrace를 시작하고 위의 소스코드를 컴파일하여 수행하면, 아래와 같은 결과를 얻을 수 있습니다.

R,0,256
R,256,256

흥미로운 것이 실제로는 128KB크기의 읽기를 한번만 요청하였지만, 연속된 128KB에 대해서도 읽기를 수행했다는 것입니다. 연속된 읽기가 발생할 수 있는 상황을 가정하여 데이터를 미리 읽어오도록 한 것입니다. 리눅스에서 어떤 상황에서 prefetch를 결정하는지는 잘 모르겠습니다. 더욱 재밌는건 처음의 쓰기 후 읽기 패턴에서는 prefetch를 수행하지 않다가 지금의 읽기만 있는 패턴에서는 prefetch를 결정했다는 것입니다.

한가지 당연한 진실은 prefetch라는 것은 버퍼링이 수행된다는 가정에서만 가능하다는 것입니다. 따라서 Direct IO에서는 별 의미가 없습니다. 바보 같지만 실제로 같은 실험을 O_DIRECT플래그를 주고 실험해 보겠습니다.

  1. /* odirect_prefetch.c */  
  2. #define _GNU_SOURCE  // O_DIRECT  
  3. #define IOUNIT 128*1024  
  4.   
  5. #include   
  6.   
  7. #include  
  8. #include  
  9. #include   
  10.   
  11. int main()  
  12. {  
  13.   char *nBuf;                // not aligned buffer  
  14.   char *aBuf;                // aligned buffer  
  15.   int   fd;                  // file descriptor  
  16.   
  17.   // 1MB buffer (not aligned yet)  
  18.   nBuf = (char*) malloc(IOUNIT+getpagesize());  
  19.   // aligned buffer 'aBuf'  
  20.   aBuf = (char*)((unsigned)(nBuf+getpagesize()-1)/getpagesize()*getpagesize());  
  21.   
  22.   printf("OPEN FD: %d\n", fd=open("[블록장치경로]", O_RDWR | O_DIRECT ) );  
  23.   printf("READ: %d\n", read( fd, aBuf, IOUNIT ) );  
  24.   close(fd);  
  25.   
  26.   free(nBuf);  
  27. }  

성실하게 위의 프로그램을 수행하면 아래의 결과를 확인할 수 있습니다.

R,0,256

버퍼링을 하지 않으므로 prefetch 효과도 발생하지 않습니다. (참고로) 프로그램을 반복 수행하여도 읽기 IO를 정직하게 실제로 계속 수행합니다.

결론

정리하면 다음과 같습니다.일반적으로 운영체제에서는 IO발생시 IO요청을 버퍼링하여 성능을 향상시키려 합니다. 이로 인해 사용자가 쓰기 연산을 수행하고, 사용자에게는 연산을 완료했다고 리턴하면서도 장치에는 즉각 반영되지 않는 일이 발생합니다.

사용자 입장에서는 당황스러울 수 있지만, 다음과 같은 IO 성능 향상의 기회를 얻을 수 있습니다.

  • 같은 번지수의 IO가 짧은 시간내에 반복될 경우, 한번만 반영할 수도 있습니다.
  • 혹은 연속된 주소의 다수의 IO가 발생하면 이를 모아서 처리할 수 있습니다.
  • 혹은 (디스크의 경우) IO요청을 블록 주소로 정렬하여 반영하면 성능이 향상될 수 있습니다.
  • 그외에도 IO를 지연시킴으로써 다른 최적화 할 수 있는 기회를 얻을 수 있습니다.

하지만 다음과 같은 이유로 버퍼링을 부분적으로 제한하거나 아예 버퍼를 무시하고자 합니다.

  • Synchronous IO: 응용프로그램의 특성에 따라 사용자는 자신의 요청을 즉각적으로 반영하길 원합니다. 이 경우 O_SYNC플래그를 이용하거나 fsync()와 같은 함수를 이용할 수 있습니다. 알티베이스와 같은 DBMS 소프트웨어의 경우에는 트랜젝션 로그를 반영하는 일들이 그에 해당될 수 있습니다.
  • Direct IO: 일부 자신만만한(?) 응용프로그램(DBMS가 대표적입니다)은 운영체제의 버퍼 자체가 없었으면 하기도 합니다. 이 경우 리눅스에서는(!) 근래의(?) 커널에서 O_DIRECT 플래그를 이용할 수 있습니다.

DBMS에서 Direct IO를 사용하는 이유는 DBMS자체가 내부적으로 버퍼를 관리하고, 이게 알고리즘이 운영체제에서 하는 것보다 우수하다고 믿기 때문입니다. 운영체제에서 IO를 버퍼링하게 되면 같은 디스크 블록에 대해 중복으로 버퍼링하므로 낭비이고, 이게 성능을 저하시킨다는 것입니다. 추가로 확인하신 prefetch의 효과 또한 DBMS에게는 낭비일 수 있습니다. DBMS는 이미 자신이 연속된 블록을 읽을지 임의의 블록을 읽을지 알 수 있어 스스로 IO의 단위를 조정할 수 있기 때문입니다.

그렇다고 항상 Direct IO가 성능이 우수한 것은 아닌 모양입니다. 최근 엑셈의 조동욱님께서 관련된 글을 찾아서 블로그에 링크시켜 놓으신게 있습니다. 글을 읽어보시면, DBMS 버퍼의 크기를 너무 작게 설정하여 빈번한 IO가 발생하는 경우, 운영체제 버퍼를 이용하는 것이 더 좋은 성능을 보인다는 어쩌면 당연한 이야기입니다.

실험 코드를 포함하다 보니 생각보다 글이 길어졌군요. (지루하지 않으셨길 바랍니다.)
모두 좋은 한주 보내세요. 다음에 뵙겠습니다.

알티스토리의 외관에 대한 작은 생각
개발자 세상

개발자들은 디자인에는 별로 소질이 없다는 것이 일반적으로 받아들여지는 상식입니다. 나 역시 그렇습니다. 뭔가 새로운 것을 그려내거나 하는 데에는 큰 소질이 없지요. 그렇지만, 야구 경기를 즐기기 위해서 반드시 야구 선수가 되어도 좋을 정도로 야구를 잘 할 필요는 없는 것 아니겠습니까? 개발자들이 디자인을 “하는” 것은 잘 하지 못할지도 모르지만, 좋은 디자인을 “보는” 눈까지 없는 것은 아니라고 생각합니다.

한시간 전에 퇴근해서 졸리지만, 알티 스토리가 갑자기 생각나더군요, 그래서 이 기회에 알티 스토리가 사용하는 스킨에 대해 몇가지 불평을 하는 포스팅을 좀 하겠습니다.

첫째. 내가 사용하는 브라우저인 파이어폭스에서는 잘 안보인다는 점이 가장 불편한 점이에요. 이것은 시각적 취향과 품격의 문제를 떠난, 실제적인 문제인 것이죠. 아래의 스크린샷처럼 첫 글짜가 잘려서 짐작으로 읽을 수 밖에 없습니다. ㅠ_ㅠ 저도 잘리지 않는 제대로 된 텍스트를 읽고 싶어요 ㅠ_ㅠ.



둘째. 이것은 그다지 큰 문제가 아닐지도 모르지만, 저처럼 외형적인 아름다움에 상당히 민감한 사람의 경우 상당히 신경쓰이는 문제일 수도 있습니다. 물론, 파이어폭스에서 나타나는 현상입니다. 아래의 그림에서 볼 수 있듯이 필요없는 “공지” 부분이 두개나 나오는 문제입니다. IE 에서는 나오지 않더군요 ㅠ_ㅠ

둘째. 이것은 그다지 큰 문제가 아닐지도 모르지만, 저처럼 외형적인 아름다움에 상당히 민감한 사람의 경우 상당히 신경쓰이는 문제일 수도 있습니다. 물론, 파이어폭스에서 나타나는 현상입니다. 아래의 그림에서 볼 수 있듯이 필요없는 “공지” 부분이 두개나 나오는 문제입니다. IE 에서는 나오지 않더군요 ㅠ_ㅠ


언뜻 보아서도 뜨는 부분 없이 자연스럽게 매치가 되지요? ^^;

그러나, 지금 알티스토리는 커스텀 배경으로 인해 아래 사진들에서 보이듯이 조화롭지 않은 모습을 보여주고 있으며, 이 역시, 기능을 사용하지 못한다든지, 컨텐츠가 보이지 않는다든지 하는 문제는 없지만, 신경이 쓰입니다.

가운데 왼쪽에 파란색 잡티가 보입니다.

그림들이 자연스럽게 배치되지 않고 깍뚜기 모양으로 어긋나 있습니다. 또한 파란색과 갈색톤의 미스매치로 인한 불안감이 조성됩니다.

커스텀 배경이미지가 타일처럼 배열되어 여기쯤에서 반복되는군요.


화면 맨 하단입니다. 역시 어울리지 않는 이미지들로 인해 테두리의 경계가 너무 확연히 드러나고 있습니다. 붕 떠 보이지요.

그런데, 외관이 정말 중요할까요?중요합니다. 그것도 매우 중요하다는 게 제 생각입니다. 물론, “Beauty is merely a skin deep” 이라는 격언도 있습니다만 (제대로 기억하는 건 아닐 겁니다만, 비슷하게 기억하긴 했을 겁니다 ^^) 최근의 iPhone, iPod 등의 성공과 믿기지 않을 정도로 apple 을 좋아하는 소위 “애플빠”, “애플 광신도” 가 왜 생겼겠습니까?

애플 제품을 사용해 보면 겉보기에는 좋아 보이지만, 사실, ‘이건 좀 아니다’라는 생각이 드는 경우가 많습니다. 배터리도 교체할 수 없지요, 운영체제는 이상한 짓을 몰래몰래 하지요, USB 케이블도 이상한 것을 써서 자기네 케이블이 아니면 사용할 수 없지요, iTunes 가 아니면 노래 관리도 못하지요.. 등등 이루 말 할 수 없을 정도로 결점이 많은 제품이 바로 애플의 제품들입니다. 게다가 비싸기까지 하지요!

그런데도, 그와 같은 결점들을 알면서도 애플의 제품을 살 수 밖에 없게 하는 여러 가지 이유들 중 하나가 저는 “예쁘기 때문”이라고 생각합니다.

알티 스토리도 방문했을 때 stackoverflow.com 이나, 다른 기업들의 블로그 (농심KT 등등) 처럼 커스텀 스킨에, 그림도 예쁘게 그리고 하는 것을 바라지는 않지만, 최소한 위화감은 들지 않는 깔끔한 기분의 화면을 가졌으면 합니다.

에혀.. 이 시간에 뭐하고 있는지 ㅠ_ㅠ 이제 자야겠습니다 ^^;;

linux core file name 변경하기
개발자 세상

예전에 제 블로그에 올렸었던 건데, 그냥 복사해서 붙입니다.
혹시 개발하시면서 core file 이름이 전부 그냥 “core” 라서 답답해 하셨던 분이 계시다면 희소식(?) 일 겁니다. 리눅스에만 관련된 건데… 다른 플랫폼은 제가 작업하다가 불편하다고 느끼면 조사해 보겠습니다 ;-)

sun 이나 hp, aix 등에서는 생성된 core file 에다가 file 명령어를 치면 어떤 실행파일에서 생성된 core 인지가 나온다. 그러나 linux 에서는 그렇지 않다. (아쉽게도 OSX 에서도 그렇지 않다)

다음의 프로그램을 만들어서 테스트 해 본 결과

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    int *a = NULL;

    printf("hellon");
    fflush(stdout);

    *a = 32;

    return 0;
}

각각의 플랫폼에서는 다음과 같은 결과가 나왔다 :

$ uname -a;file core                                     
SunOS v880 5.8 Generic_117350-51 sun4u sparc SUNW,Sun-Fire-880
core:           ELF 32-bit MSB core file SPARC Version 1, from ‘a.out’

$ uname -a; file core
AIX aix5 3 5 0001D01F4C00
core: AIX core file 32-bit, a.out

% uname -a; file core
HP-UX hp1123 B.11.23 U 9000/800 190494686 ??????-????? ???????
core:           core ???? - ‘a.out‘ - SIGBUS ????

$ uname -a; file core.528 
Darwin castepo.local 9.3.0 Darwin Kernel Version 9.3.0: Fri May 23 00:49:16 PDT 2008; root:xnu-1228.5.18~1/RELEASE_I386 i386
core.528: Mach-O core i386

때때로 여러개의 프로세스를 마구 띄우는 daemon 을 작업하다 보면 뜬 프로세스들 중 하나가 죽을 경우에 생기는 core 파일이 도대체 어느 프로그램이 죽어서 생긴 것인지 알 수 없을 경우가 있다. 물론, 하나씩 디버거로 읽어서 뭔가 좀 말이 되는 콜스택을 보여준다든지 하는 녀석을 대충 지레짐작해서 얘이거니 하고 분석할 수도 있지만, 기분 나쁘다.

리눅스 맨페이지에 따르면 커널 버젼 2.6 이후, 그리고 2.4.21 이후의 커널에 다음의 기능이 추가되어 있다고 한다 :

$ man core | col -b 
.
.
.
   Naming of core dump files
       By default, a core dump file is    named  core,  but  the    /proc/sys/ker-
       nel/core_pattern file (since Linux 2.6 and 2.4.21) can be set to define
       a template that is used to name core dump files.  The template can con-
       tain  % specifiers which are substituted by the following values when a
       core file is created:

     %%  A single % character
     %p  PID of dumped process
     %u  real UID of dumped process
     %g  real GID of dumped process
     %s  number of signal causing dump
     %t  time of dump (seconds since 0:00h, 1 Jan 1970)
     %h  hostname (same as ‘nodename’ returned by uname(2))
     %e  executable filename

말인 즉슨, 다음과 같이 하면 core file 이 생성될 때 그 파일의 이름을 어떻게 지을 것인가 하는 문제를 사용자가 설정할 수 있다는 이야기인데, cut to the chase, 결론부터 말하면, 다음과 같이 하면 실행파일의 이름을 가지고 core 파일의 이름을 만들 수 있다 :

# cat > /proc/sys/kernel/core_pattern
core.%e
^D
#

저와 같이 해 두고 아까 컴파일한 파일을 실행시켜 보면,

$ ./a.out 
hello
Segmentation fault (core dumped)
$ ls
a.c  a.out*  core.a.out
$

아아~~ 행복하다. =ㅂ=);;;

[백서.세번째]Altibase Replication
개발자 세상

안녕하시렵니까. 불광불급입니다. 
낼 모레가 추석인데 아직도 낮 기온이 30도에 육박하다니… 날씨가 미쳤나 봅니다. 이궁… 선선한 날씨가 좋은데…

그동안 다들 무탈하셨죠? 
오랫동안 게으름을 좀 피웠네요. 말이 살찐다는 그 계절에 제가 외려 토실토실 해 지는 게 아마 게으름 탓 인가 싶기도 합니다. ^^;;

앞서 올린 백서는 알티베이스를 이해하는 데 좀 도움이 되셨는지요. 오늘은 ‘이중화’에 관한 백서를 제공해 드릴까 합니다.

다음은  알티베이스 이중화의 특징을 소개하고 있는 본문의 일부를 발췌한 것입니다. 

“알티베이스는
 Altibase 이중화를 이용해 기업의 성능뿐만 아니라 고가용성무정지 서비스,확장성  DBMS 대한 모든 요구사항을 충족시켜줍니다
Altibase
 이중화는 네트워크를 이용한 로그 기반의 이중화  다중 IP 네트워크를 지원함으로써시스템의 고가용성과 24*7 무정지 서비스를 가능하게 해줍니다또한 테이블 단위의 이중화 기능은 자원의 효율적 활용과 이를 통한 비용 절감, DBMS 성능 향상으로 이어집니다최대 32대의서버까지 지원하는 뛰어난 확장성도 자랑거리입니다
.”

자세한 내용은 다음의 첨부파일 클릭…

 Altibase5_ Replication.pdf

Altibase5_ Replication