Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

RRD를 이용한 대량 웹로그 분석 1의 후속 문서로, 구현에 대한 내용을 다룰 것이다.

구현 환경

Linux 운영체제(:12)에서 C++과 RRD(:12), SQLite(:12)를 이용해서 구현해볼 생각이다.

RRD는 시간을 축으로 하는 통계정보의 저장을 위해서 사용될건데, joinc(:12)의 각 페이지의 방문통계를 저장하는 툴을 만들어볼 생각이다. 페이지가 대략 2800개 정도가 되니, 2800개의 RRD 파일을 생성하게 될 것이다.

C++은 최근 단시간의 통계정보를 만드는 프로그램을 만들 것이다. 예를들어 RRD의 최소 저장단위가 5분이라면, 각 페이지별로 5분동안의 카운트 정보를 만들어야 할 것이다. 이 프로그램은 5분동안의 카운트정보를 만든다음에, 각 페이지에 대응되는 RRD 파일에 값을 insert 할 것이다. C(:12) 대신 C++을 선택한 이유는 거의 순전히 STL(:12)을 사용하기 위함이다.

SQLite는 TOPN 기반의 RRD의 구현을 위해서 사용한다. 구현할 서비스는 최근 일,주,월,년 간 인기 방문 페이지 통계서비스다.

우선 만들기 쉬운 페이지별 통계 시스템을 만들고, 그 뒤에 TOPN 기반의 방문페이지 통계 시스템을 만들 것이다. TOPN기반의 방문페이지 통계 시스템은 따로 문서를 만들계획이다.

페이지별 통계 시스템

통계 자료는 apache(:12) 웹로그로 할 것이다. 그러므로 우선, 실시간으로 웹로그를 처리하는 프로그램을 만들어야 한다. 이 프로그램은 지정된 웹로그 파일을 주시하고 있다가 새로운 로그가 들어오면, 적당히 파싱한다음 페이지 이름에 대해서 counting을 할 것이다.

로그 서버 준비

apache로그 자체를 분석하는 방법도 있지만, 이런 저런 잡다한 정보들이 많아서 분석하는데 쓸데없는 비용이 많이든다는 문제점을 가진다. 그래서 별도의 로그 서버(:12)를 하나 만들 생각이다. 이 로그서버는 분석에 필요로하는 로그만을 가지게 된다. 이 로그 서버의 이름은 log server이라고 하겠다. 분석해야할 로그를 전송하는 서버는 joinc server다.

전송은 img(:12) 태그(:12)를 이용할 것이다. 로그분석에 필요한 여러가지 정보를 img 태그와 함께 GET(:12) log server로 넘긴다. 그러면 log server는 이 정보를 apache access log에 남긴다.

attachment:logsysgem.png

log server는 cpu와 memory 자원을 소비할 수 있으니, 원격지에 두는게 좋겠으나 여건이 허락하지 않는 관계로 joinc server와 같은 위치에 두기로 했다. 로그양이 많을 거라고 생각되지 않으니, 그닥 문제 없을 것 같기도 하고..

IMG 태그

이미지를 로딩하는 것이기 때문에, 만약 query string를 포함한 이미지이름이 같다면, 캐쉬에 있는 것을 읽어 버린다. 이미지이름을 바꿀수는 없으므로, query string이 항상달라질 수 있도록 해야 한다. 여기에서는 페이지를 읽는 시간을 query string의 값이 달라지도록 했다.
<img src=http://..../imgs/log.png?page=111&time=1238192011&...>

RRD 파일 생성

우선 RRD 파일을 생성하는 간단한 shell(:12) 스크립트를 만들었다. 이 프로그램의 이름은 makerrd.sh 로 했다. 로그의 양이 많은게 아니므로, 최소시간 간격은 1시간으로 했다.
#!/bin/sh
#mkrrd.sh
rrdtool create ../rrd/$RRDFILE --start 1232247600 --step 3600 \
DS:count:GAUGE:7200:U:U \
RRA:AVERAGE:0.5:1:48 \
RRA:AVERAGE:0.5:24:360 \
RRA:AVERAGE:0.5:720:36
이제, 위키페이지가 저장되어 있는 디렉토리의 파일을 읽고, 그 파일이름을 인자로 해서 makerrd.sh를 실행시키는 간단한 perl(:12) 스크립트를 만들었다.
#!/usr/bin/perl -w

$dirname = $ARGV[0];
opendir(DIR, $dirname) || die "Error in opening dir $dirname";

while($filename = readdir(DIR))
{
  if(!($filename =~ /^\./))
  {
    system("./makerrd.sh $filename");
  }
}
close(DIR);

RRD Update에서의 문제점

RRD툴은 heatbeat 시간내에 데이터가 들어오지 않을 경우, 값을 nan으로 설정을 한다. 값이 nan으로 설정될 경우에는 측정된 값이 없는 것으로 판단하여, 통계데이터로 간주가 되지 않는다.

이것은 문제가 될 수 있다. 왜냐하면, 이 경우에 nan은 측정값이 없다라는 의미가 아닌, 0으로 측정되었다라는 의미로 받아들여야 하기 때문이다. 간단히 생각해서 다음과 같은 데이터가 있을 때
1231412400: 1.1000000000e+01
1231416000: 1.1000000000e+01
1231419600: nan
1231423200: nan
1231426800: nan
1231416000: 1.1000000000e+01
우리가 원하는 평균값은 (1.1+1.1+1.1)/6 이지만, 실제로는 (1.1+1.1+1.1)/3이 되어 버린다.

RRD툴은 이러한 방법을 자동적으로 해결해주지 않는 것 같다. 어쩔 수 없이 update하는 시점에, last update 시점을 찾아낸 다음에 update와 lastupdate 사이에 있는 모든 값을 0으로 만드는 방법을 사용하기로 했다. 즉 위의 예의 경우, 1231416000: 1.1000000000e+01 을 update 해야 하는 시점에 last update가 1231416000: 1.1000000000e+01 이니, 그 사이에 있는 값들을 전부 0으로 설정해준다. 최종적으로 아래와 같은 데이터가 쌓이게 될 것이다.
1231412400: 1.1000000000e+01
1231416000: 1.1000000000e+01
1231419600: 0
1231423200: 0
1231426800: 0
1231416000: 1.1000000000e+01

로그 서버 준비

log.joinc.co.kr 을 하나 만들고, log_access.log로 apache access log가 쌓이도록 했다. joinc server에 img 태그를 달았더니, 다음과 같은 로그가 남는게 확인되었다.
...
59.18.203.132 - - [11/Jan/2009:13:02:16 +0900] "GET /imgs/log.gif?page=man_2f12_2fIPv6&time=.... HTTP/1.1" 200 62
59.18.203.132 - - [11/Jan/2009:13:02:26 +0900] "GET /imgs/log.gif?page=Site_2fTCP_5fIP_2fIPv6_2fIPv6Env&time=... HTTP/1.1" 200 62
211.187.253.41 - - [11/Jan/2009:13:02:59 +0900] "GET /imgs/log.gif?page=pthread_20_bf_b9_c1_a61&time=... HTTP/1.1" 200 62
...
이제 분석프로그램을 만들 차례다.

분석 프로그램

C++로 만들 것이다. log server가 남기는 access 로그를 지켜보다가. 새로운 log가 발생하면, 분석해서 counting을 한다. 그러다가 1시간 지나면, 이 값을 RRD 테이블에 쓴다. 분석프로그램은 tail 함수를 약간 수정해서 제작했다.

대략 돌아가는게 확인되어서, 현재 joninc 로그를 분석은 하고 있지만 코드가 지저분한 관계로 아직은 공개하지 않을 생각이다. 좀 더 다듬어서 공개해볼까 생각한다. 이미 바로 사용할 수 있는 tail 함수도 있으니, 코드를 만드는 건 그닥 어렵지 않을 것이라고 생각된다.

적용 결과

아래 데이터는 Average 이므로, 출력값을 조절해줘야 할 것이다. 통계가 있는 페이지는 아래의 그래프가 뜨도록 했다. 어느정도 통계자료를 만들면, 주/월/년 자료를 만들어볼 생각이다. 그리고 위의 코드도 좀더 다듬을 계획이다. 그래프는 google:::chart(:12)를 이용해서 생성했다.