Aug 12 2007

자격지심…

분류: Dev.Think 태그: ,, , Heart @ 2:53 오전

Trackback : http://dev.heartsavior.net/archives/87/trackback/

왠지 시간이 지날수록 프로그래머라는 직업에 대해서 내가 느끼는 자격지심이 커지는 것 같다.

내가 프로그래밍이라는 것을 처음 알았던 초등학교 2학년 때부터 지금까지를 돌아보면 나의 위치를 재는 비교대상이 점점 커져가고 있음을 느낀다.

그때는 컴퓨터학원에서 진도가 가장 빨랐던 것만으로도 자신감을 가지고 살 수 있었는데, 초등학교 6학년때 교육청 경진대회, 서울시 경진대회를 나가면서 날고 기는 사람들이 많다는 것을 처음으로 실감했다.

그리고는 대학 때까지 학업 때문에 프로그램을 접하지조차 못했다. 비교를 할 데가 없으니 자신감이랄 것도 없었다.

그렇게 학창시절을 보내고, 컴퓨터학부로 대학 입학을 하고, 당분간은 동기들에게 인정을 받았다. 어쩌면 당연했다. 먼저 해 놓은게 있으니…
하지만 그것은 동기들이 평가한 나였을 뿐이고, 학교 생활을 하다 보니 잘 나가던 학부 선배들이 눈에 들어오기 시작하고, 그게 전 대학으로 확장되니까 나 자신을 ‘그저 그런’ 정도로 생각하게 되었다.

그러다가 산업기능요원으로 취직을 하게 됐고, 실무에 발을 들이니 다른 프로그래머 이야기들이 관심이 가게 된다. 외국 프로그래머와 한국 프로그래머 모두…
비교 대상이 모든 프로그래머가 된 것이라고 해야 될까…
그렇다보니 정말 어떤 포스트를 봐도 대단해 보이고, 그러면서 ‘난 이정도가 되려면 어떻게 해야 되나…’ 좀 심한 경우는 ‘난 안될라나…’ 까지 생각하기도 한다.
즉, 자격지심을 넘어선 열등감도 많이 느끼곤 한다.
그런가 하면, (전체적으로나, 혹은 경력에 비해) 미숙한 프로그래머들도 많이 보기도 해서 나 자신에 대한 자만심도 들 때도 생긴다.

아이러니하다.
이제는 내 실력이 어느 정도인지, 나의 위치가 어느 정도 되는지도 잘 모르겠다.

아무튼 최근에 계속 머릿속에 박히는 건 ‘평범’ 이라는 두 글자이다.
‘평범’하게 하다 끝내려고 시작한 게 아닌데, 왠지 그 쪽으로 가고 있는듯한 불안감이 든다.

그래도 나라는 사람이 유일하게 제대로 할 줄 아는 건 프로그래밍밖에 없는데, 이걸로 누구한테 밀리고 싶지는 않다.

내게 남은 마지막 자존심이랄까?


Aug 09 2007

Log4j DailyRollingFileAppender 개선 - Failed to rename 에러 최소화하기

분류: Open.Library 태그: ,, , , , Heart @ 11:04 오후

Trackback : http://dev.heartsavior.net/archives/88/trackback/

Log4j 의 Appender로 DailyRollingFileAppender를 사용할 경우에 파일 교체 시기에 ‘Failed to rename’ 이라는 에러를 만나게 되는 경우가 있습니다.
이 에러가 발생하면 새로운 내용은 기존 파일에 그대로 쓰여지지만 기존 로그 파일의 내용이 전부 날아가는 심각한 상황이 발생하죠. 물론 기존 내용이 날아가므로 파일 교체 기능도 제대로 수행하지 못하구요.

저도 이 문제로 약간 고민하다가, 문제를 바로 해결할 수 있을까 해서 구글링을 시도했었는데…
관련 페이지가 엄청나게 많더군요. 많은 사람들이 이 문제로 고민을 했다는 반증이라고 생각됩니다.
아무튼 관련 페이지는 엄청나게 많은 반면 깔끔하게 해결이 되는 것은 찾을 수가 없었습니다. 대신, 에러가 발생하는 원인을 지적하는 내용은 많았기 때문에 계속 찾아보는 것보다 원인에 대한 해결책을 구현해 보는 것이 낫겠다는 생각을 하고 구현으로 방향을 선회했습니다.

원인은 바로, 같은 파일을 여러 로그 인스턴스에서 참조하는 경우에 각 로그 인스턴스에서 로그 파일을 인스턴스가 정리되거나 파일 교체가 일어날 때까지 놓지 않기 때문이었습니다.
(저는 설정 파일에 여러 카테고리를 설정하고 모든 카테고리에서 하나의 로그 파일을 사용하도록 하니까 이런 문제가 생기더군요. 물론 각 카테고리마다 인스턴스를 하나씩 생성했죠.)

아래는 Log4j 1.2.14의 DailyRollingFileAppender.java 의 rollOver() 에서 주석을 제거한 내용입니다.

void rollOver() throws IOException {

if (datePattern == null) {
errorHandler.error(”Missing DatePattern option in rollOver().”);
return;
}

String datedFilename = fileName+sdf.format(now);
if (scheduledFilename.equals(datedFilename)) {
return;
}

this.closeFile();

File target  = new File(scheduledFilename);
if (target.exists()) {
target.delete();
}

File file = new File(fileName);
boolean result = file.renameTo(target);
if(result) {
LogLog.debug(fileName +” -> “+ scheduledFilename);
} else {
LogLog.error(”Failed to rename ["+fileName+"] to ["+scheduledFilename+"].”);
}

try {
this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
}
catch(IOException e) {
errorHandler.error(”setFile(”+fileName+”, false) call failed.”);
}
scheduledFilename = datedFilename;
}

DailyRollingFileAppender에서는 파일을 바꿔야 할 시기가 되면 파일을 닫고 파일 이름 변경을 시도합니다.(즉, 파일을 계속 잡고 있는다는 이야기…)
그렇기 때문에, 다른 로그 인스턴스 중에 같은 파일을 계속 잡고 있다면 자신만 closeFile()로 파일을 닫는다고 해도 File.renameTo()가 정상적으로 동작할 수가 없는 것이죠.

가장 간단하게 생각할 수 있는 방법은 rollOver() 에서 파일을 닫을 때, 같은 파일을 사용하는 모든 로그 인스턴스에서 파일을 닫는 것입니다. (물론 새로운 파일을 연결해 주는 것도 잊지 말아야 하겠죠?)

이 방법을 나름대로 간단하게 구현하기 위해, DailyRollingFileAppender의 내용을 붙여서 새로운 클래스를 만들었습니다.
(rollOver() 메소드의 한정자가 패키지이기 때문에, 새로운 클래스를 Log4j 라이브러리에 포함하고 상속받아서 구현할 수도 있겠지만 새로운 클래스를 정의하여 내용을 그대로 Copy & Paste 하고 필요한 부분만 수정하였습니다. Log4j의 jar 파일에 포함하는 것은 Ant 사용법을 안다면 껌이기 때문에 여러분의 몫입니다…^^;;)

구현 방법을 살펴봅시다.

우선 모든 로그 인스턴스를 제가 필요로 할 때 찾을 수 있어야 하기 때문에 아래와 같은 static attribute를 추가합니다.

private static ArrayList<FixedDailyRollingFileAppender> arrAppender = new ArrayList<FixedDailyRollingFileAppender>();

다음으로, 모든 생성자에서 아까 만든 ArrayList로 객체를 포함시킵니다.

FixedDailyRollingFileAppender.arrAppender.add(this);

마지막으로 rollOver()를 수정합니다.

synchronized // For multiple instance
void rollOver() throws IOException {
if (datePattern == null) {
errorHandler.error(”Missing DatePattern option in rollOver().”);
return;
}

String datedFilename = fileName+sdf.format(now);
if (scheduledFilename.equals(datedFilename)) {
return;
}

Iterator<FixedDailyRollingFileAppender> iter =             FixedDailyRollingFileAppender.arrAppender.iterator();

while( iter.hasNext() ) {
FixedDailyRollingFileAppender appender = iter.next();

// Windows System에서는 대소문자 가리지 않음…
// Unix System에서는 compare()를 사용하면 됨
if( this.fileName.compareToIgnoreCase(appender.fileName) == 0 && appender.fileName.length() == this.fileName.length() ) {
appender.closeFile();
}
}

// 위에서 이미 닫음
//this.closeFile();

File target  = new File(scheduledFilename);
if (target.exists()) {
target.delete();
}

File file = new File(fileName);
boolean result = file.renameTo(target);
if(result) {
LogLog.debug(fileName +” -> “+ scheduledFilename);
} else {
LogLog.error(”Failed to rename ["+fileName+"] to ["+scheduledFilename+"].”);
}

iter = FixedDailyRollingFileAppender.arrAppender.iterator();

while( iter.hasNext() ) {
FixedDailyRollingFileAppender appender = iter.next();

// Windows System에서는 대소문자 가리지 않음…
// Unix System에서는 compare()를 사용하면 됨
if( this.fileName.compareToIgnoreCase(appender.fileName) == 0 && appender.fileName.length() == this.fileName.length() ) {
try {
appender.setFile(fileName, true, this.bufferedIO, this.bufferSize);
}
catch(IOException e) {
errorHandler.error(”setFile(”+fileName+”, true) call failed.”);
}

appender.scheduledFilename = datedFilename;
}
}
}

구현한 내용을 요약하면…
1. 파일을 닫을 때 같은 파일을 사용하는 로그 인스턴스를 발견해서 모두 파일을 닫아 줌
2. 파일을 변경
3. 닫아준 인스턴스를 다시 찾아서 파일을 다시 설정해 줌

이 되는 것이죠.

멀티쓰레드의 경우 파일을 닫고 변경하고 설정하는 과정 중에 같은 파일을 사용하는 로그 인스턴스 두 개 이상이 rollOver()로 진입하면 소위 꼬일 수가 있으므로 rollOver()에 synchronized 키워드를 넣어 주었습니다.

구현된 전체 내용입니다.

전체 구현 보기..

깔끔하지는 못한 구현이지만 이런 문제로 고생하고 있는 분들에게 도움이 되었으면 합니다. ^^


Aug 08 2007

태터툴즈 설치형 - 블로그아이콘/파비콘에 의한 CPU, DB 과부하 발생 원인과 해결방안

분류: Dev.Think 태그: ,, , , , Heart @ 10:19 오전

Trackback : http://dev.heartsavior.net/archives/89/trackback/

유료 계정(마루 호스팅)을 사용하고 있는 메인 블로그가 7일 오후 2시경 부터 서비스가 되지 않는 상황이 발생하였습니다.
호스팅 업체로부터 2시 반쯤에 전화 받고 알았습니다. 허허;;

호스팅 업체분 말씀으로는 CPU 과부하 상태인데, DB 액세스가 상당히 많은 상태라고 하더군요. 그리고는 아파치 로그와 MySQL 로그 던져주고 ‘원인 해결하면 전화주세요’ 라고 이야기하시고는 끊으시더군요.(무심하기도 하시지… 엉엉)

아파치 로그 파일을 보니, 최근에 댓글하고 트랙백을 달아둔 다른 분 블로그에서 제 계정으로 index.gif(블로그 아이콘)에 대한 요청이 초당 4~5번 정도로 일어나고 있었습니다.
(실제로 가보니 그 페이지 댓글이 몇천개더군요… 그럼 페이지 히트는 몇만 대였겠죠.)
이건 단순한 파일 요청 폭주니까 큰 영향이 없을 거라 생각했는데…

MySQL 로그 파일을 보니 태터 세션 테이블 액세스쪽이 폭주가 일어나고 있었습니다.
시간이 찍혀 있지 않았지만 왠지 느낌상 동시에 일어난 일 같다는 생각을 했습니다.
하지만 gif 파일을 요청하는데 왜 DB 액세스가 일어나는지에 대해서는 태터 구조를 모르니까 확인하기가 쉽지 않더군요.

그래서 TNF에 질문했습니다.

inureyes님(TNC 직원 분이신가요?)께서 고맙게도 적절한 답변을 남겨 주셨습니다. 그리고 적극적으로 해결하려는 움직임도 보여 주시는 것 같구요.(버그 트래킹에 등록하셨으니… 언젠간 해결될지도…)

여기까지는 사건 히스토리를 주절거려 봤구요. inureyes님의 답변을 제 나름대로 정리해 봅니다.

index.gif 요청은 블로그 설치폴더/.htaccess의 URLRewrite를 통해 blog/index.gif.php로 포워드되고, 해당 php에서 파싱을 통해 아이콘을 전달한다고 합니다. 이 때 파싱 작업 중에 세션 테이블을 사용한다네요.
(제 생각에는 파싱 작업보다는 DB 액세스 부분에서 CPU 부하가 더 있을 것 같습니다.)

마찬가지로 favicon.ico 요청 또한 블로그 설치폴더/.htaccess의 URLRewrite를 통해 blog/favicon.ico.php로 포워드되고 똑같은 작업을 합니다.

아직 해결된 버전은 없고 응급처치 방안은 두 가지가 있습니다.
(둘 중 하나만 하시면 됩니다)

1. blog/index.gif.php와 blog/favicon.gif.php 파일의 이름을 바꿉니다.

2. .htaccess 파일을 찾아서

RewriteRule ^favicon\.ico$ blog/favicon.ico.php [E=SURI:1,L]
RewriteRule ^index\.gif$ blog/index.gif.php [E=SURI:1,L]

이 두 라인 앞에 #을 붙여서 주석처리합니다.

첫 번째 방법은 타 사이트에서 자신의 블로그 아이콘이나 파비콘이 나타나지 않습니다. (당연하겠죠?)

두 번째 방법은 favicon.ico와 index.gif를 설치 폴더 루트에 따로 준비하시면 블로그 아이콘이나 파비콘이 나타날 것이고(gendoh님이 알려주셨고, 단독블로그 사용하시는 분들만 가능합니다.), 그냥 위의 방법대로만 하시면 첫번째 방법과 마찬가지의 효과가 나타납니다.

유료 계정 돈 내고 쓰면서도 호스팅 눈치 보는 신세인데…
조금이라도 부하가 걸릴 만한 것들은 미리 제거하는 센스가 필요한 것 같습니다. ^^