Framework/Database2009. 3. 19. 19:10

  public void testStatic() throws Exception {
    DBManager dbm = new MSSQLPoolDBManager("com.microsoft.jdbc.sqlserver.SQLServerDriver", 
         
"jdbc:microsoft:sqlserver://novision:1433;DatabaseName=test", "bleu", "bleu") ;
    Connection conn = null;
    ResultSet rs = null;
    PreparedStatement pstmt = null;

    try {
      conn = dbm.getConnection()
      pstmt = conn.prepareStatement(getProcSQL());
      rs = pstmt.executeQuery()
      populate(rs)
    catch (SQLException ex) {
      throw ex;
    finally {
      closeWithoutException(rs);
      closeWithoutException(pstmt);
      dbm.freeConnection(conn)
    }
  }

  private String getProcSQL() {
    return "select custNo, fixLength from customer_tblc";
  }

  private void populate(ResultSet rowsthrows SQLException {
    int i = 0;
    while (rows.next() && i++ < 10) {
      System.out.print(rows.getString(1));
    }
  }


위 코드가 customer_tblc에서 10건을 읽어서 화면에 쓰는 프로그램이 아니라는 얘기는 이미 한적이 있다. 위 프로그램은 실제로는 customer_tblc를 모두 읽어서 client에게 Network를 통해 모두 전송해주고 client는 단지 10건만 필요한데도 불구하고 모두 받아서 버려야 하는 바보같은 코드이다.

만약 sensitive 방식을 사용하면 이번에는 customer_tblc를 모두 읽어서 데이타 베이스의 temporary space에 Set 형태로 저장해서 요청시마다 한건씩 건네주는 Static 커서를 사용한다. 나중에 다시 이야기 하겠지만 Static 커서는 특정 상황에 따라 유용성이 있을 수도 있지만 위의 상황에는 적합하지 않다. 

원하는 것은 화면에 찍을 "일부"만 읽어서 "일부"만 전송이 되는 방식이다. 이렇게 하기 위해서는 전혀 생뚱맞을지 모르지만 위 코드의 경우 JDBC 문자열을 바꿔야 한다.

jdbc:microsoft:sqlserver://novision:1433;DatabaseName=test;selectMethod=cursor 와 같이 JDBC URL에 selectMethod=cursor 이란 문자열을 추가해야 한다. 이 방식의 커서에 대한 명칭은 디비마다 제멋대로 이므로 이 글에서는 Anonymous 커서라고 부르자. 원칙적으로 selectMethod=cursor는 트랜잭션에 관련한 프로퍼티이지만 row level 락의 문제로 커서에도 영향을 준다.



Anonymous 커서 동작은 위와 같다.(오라클의 아키텍쳐 이름이지만 MSSQL도 동작방식은 같다. 다른 DB는 아키텍쳐 동작에 관한 문서가 없다시피 해서 단지 추측만 할 수 있다.)

1. 쿼리를 실행한다.
2. 쿼리의 결과에 관계하는 "일부 블럭"이 Buffer Cache 메모리에 올라간다.(MSSQL은 Page)
   FetchSize만큼의 Row만 Set형태로 임시 공간에 PreLoad하고 커서는 첫번째 row를 가르킨다.
3. Client가 request fetch를 한다.
4. Server는 현재 Temporary에 있는 일부 Set의 현재 row를 리턴한다.
5. Client는 confirm message를 발송한다.
3-5 동작을 반복한다. 만약 fetchSize 이상의 row를 요청하면 2번이 한번 더 실행된다. 즉 만약 FetchSize가 10이라면 2, (3, 4, 5) * 10, 2, (3, 4, 5) * 10, 2 ... 와 같은 방식으로 실행된다.
6. 커넥션을 닫기 전에 임시 저장공간의 Set과 SQL AREA의 cursor 정보를 정리한다.


단지 selectMethod=cursor만 추가하여 첫번째의 글과 같은 방식으로 테스트를 해보면
connection : 2360
execute : 78 (2438 - 2360)
populate : 31 (2469 - 2438)
close : 0 (2469 - 2469)
가 나온다.

이전의 forward only cursor의 실행결과는 아래와 같았다.
connection : 2375
execute : 47 (2422-2375)
populate : 31 (2453-2422)
close : 6616 (9069-2453) <- 모든 데이타를 읽고 전송하느라 close 하는데에 오래 걸린다.


연결하는데 만드는데 2.3초가 걸렸지만 풀링을 만드는건 단 한번만 하는 동작이므로 역시 이는 신경쓸 필요 없다. 주의해서 볼것은 실행이 0.078초 그리고 close까지 완료하는데 총 0.1초정도 걸렸다. 데이타량도 약 58만건으로 변동이 없고 갑자기 DB가 눈부시게 빨라져서도 아니고 단지 부분범위 처리 Select를 했기 때문이다. 그리고 일부만 Client에게 전송하였다. 단지 커서만 바꿨을 뿐인데 처음의 default cursor와(forward only) 7초와 0.1초의 약 70배 차이가 발생한다. 이는 58만건일때의 차이이므로 만약 데이타가 더 많다면 그 차이가 더 벌어진다. 





네트워크 패킷 캡쳐 화면을 보면 이 사실은 더욱 명확해진다. 이전의 forwardOnly는 모든 row의 데이타를 받았기 때문에 패킷의 수가 아주 많았지만 Anonymous 방식은 단지 수백개의 패킷만을 받았다. 3개식 순서대로 나타나는 것은 fetch와 send가 동기적임을 뜻한다. request Fetch by Client -> Send Packet By Server -> confirm message by Client 이 3가지 동작이 반복해서 일어난다.

--
잠깐 상관없는 얘기를 하자면 성능과 관련해서 테스트를 할때는 항상 총알을 2발이상 쏴야 한다. 암살자가 저격을 할때 영화나 만화에서처럼 이마를 겨냥해서 딱 한방을 쏘고 폼나게 돌아서는 짓은 아주 바보같은 짓이다. 저격 포인트라는 것 자체가 저격하기 쉽지 않다는 걸 뜻하고 정말 한방에 목표를 죽이는게 쉽지 않기 때문에 총은 2발 이상을 쏘는게 좋다. 즉사가 아니더라도 과도한 피출혈로도 죽일 수 있기 때문에 가능하면 여러발을 쏘는게 좋다. 테스트를 할 때도 마찬가지이다. 위 테스트들은 한번 한게 아니라 기관총을 쏘듯 수천번을 테스트 한 결과이고 평균치는 거의 변함이 없다.
--


마땅한 명칭이 없어서 Anonymous라고 지칭했지만 명칭만 제멋대로 인게 아니라 이 커서를 사용하기 위한 방법도 제 멋대로다. selectMethod=cursor는 마법의 지팡이가 아니기 때문에 JDBC URL에 단순히 이 문장을 써준다고 빨라지는 것은 아니다. 많은 상황에서의 단지 하나의 예일 뿐이다. (예컨데 ResultSet.TYPE_SCROLL_SENSITIVE 방식을 사용하게 되면 Static 커서를 사용하게 되므로 영향을 미치지 않는다. )

MSSQL2000은 연결 문자열을 바꿔야 하지만 이를테면 오라클은 이 글의 Anonymous 방식이 default이다. 어떤 DB는 SQL을 바꿔야 하고 또 어떤 드라이버를 사용하면 안되고 다른 드라이버를 사용해야 할때도 있었다. MSSQL도 2005버전이상이 되면 selectMethod=cursor로 되지 않는다. DB마다 버전마다 드라이버마다 다르기 때문에 매 프로젝트마다 이를 테스트 해보고 방법을 찾아야 했다. 그리고 일부 디비는 이런 처리방식이 애초에 존재하지 않는 경우도 있었다. (일단 DB가 부분범위 처리를 지원하지 않으면 안되고 드라이버에서 부분범위 처리를 구현했어야 한다.)

위 방식의 장점은
1. 원하는 만큼만 select를 해서 원하는 만큼만 전송이 이루어지기 때문에 상대적으로 매우 빠르게 동작한다.
실제 필요한 부분이 쿼리셋의 단지 부분일 뿐이라면 그  결과에 대해 모두 알 필요는 없다는 것이 위 방식의 핵심이고 Anonymous cursor를 사용하는 이유가 된다.



단점은

1. 매 fetch시마다 3번의 Network 통신이 발생한다.
이 문제의 해결은 다음글에 적기로 하자.



2. 항상 쓸 수 있는 방식이 아니다. 쿼리의 종류에 영향을 받는다.

항상 쓸수 있는 방식이 아니라는 얘기는 이렇다. 이를테면 부서별 사원수를 알고 싶다고 하자.

select deptno, count(*) from emp group by deptno 와 같은 쿼리는
흔히 전체범위 쿼리라고 불리는데 전체를 읽지 않고서는 제대로 된 결과를 얻을 수 없다. 그래서 위와 같은 쿼리는 사용자의 선택에 상관없이 Static 방식의 커서가 사용된다. 그러나 조금 생각을 달리 해볼수도 있다.

이를테면 부서가 아주 세분화되서 수천개에 이르고 역시 화면에 보고자 하는 것은 그 일부분이라고 하자. 그럴경우 내가 보고자 하는 부분은 일부 부서이지만 쿼리는 전체 부서를 대상으로 실행이 된다. (전체범위 쿼리의 대표적인 예는 group by, order by, 집계함수 등이 있다.)

그럴경우
select deptno, (select count(*) from emp where deptno = x1.deptno ) from dept x1
와 같이 부분범위 처리가 가능한 쿼리로 바꾸고 Anonymous 커서 방식을 사용할 수도 있다. 요는 응용의 문제이므로 이 쿼리는 전체범위 처리이므로 어쩔수없어 라고 단념할 필요는 없다.

같은 쿼리지만 전체범위용 쿼리와 부분범위용 쿼리로 자유자재로 바꿀수 있는 것은 개발자로서 아주 중요한 능력이다. 이를테면 성적테이블을 대상으로 해서 79점 이하의 점수가 없는 학생의 리스트를 얻고자 한다고 할때

전체 범위 처리용 쿼리는

select 학생번호
from 성적테이블
group by 학생번호
having decode(sign(점수 - 80), -1, -1, 0) >= 0
....와 같이 작성해야 하지만


부분 범위 처리용 쿼리는
select 학생번호
from 학생테이블 x1
where not exists(select 1 from 성적태이블 where rowunum = 1 and 학생번호 = x1.학생번호 and 점수 < 80)
.....와 같이 작성해야 한다.

블랙박스 주장하는 사람에게는 애석하겠지만 이와 같이 커서방식과 쿼리는 서로 영향을 받게 된다. 달리 말해서 개발자는 자신이 실행하는 쿼리의 형태를 알아야 하고 이에 따라 적절한 커서 방식을 선택해야 한다. (위 2개의 쿼리는 모델의 mandatory의 여부에 따라 미묘하게 결과가 달라질 수 있다.)




아마도 여기서
"커서방식에 따라 많은 성능차이가 발생하기 때문에 DB, 버전, 드라이버마다 틀린 위 방법을 각자 찾아서 알아서 사용해라"
라고 끝이라고 생각했을지 모르지만 진정 흥미로운 부분은 다음 글 부터이다.


'Framework > Database' 카테고리의 다른 글

Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Framework - 커서(Keyset, Dynamic)  (0) 2009.03.13
Framework (Servant)  (0) 2009.03.12
Framework (DBController)  (0) 2009.03.12
Posted by bleujin
Framework/스팸 필터링2009. 3. 16. 07:36

단순히 하나의 글을 가지고 이 글이 스팸인가 아닌가를 판단하는 것은 무척 어렵다. 잘 알려진 폴 그레이엄의 베이지언 스팸 필터 알고리즘은 스팸글에서 특정 낱말의 스팸 점수을 구하고 이 데이타를 바탕으로 하나의 문장의 스팸 확률을 판단하는 것이다. 몇가지 수학공식이 있고 스팸 신고를 통해 스펨글의 베이스 데이타는 점점 늘어나기 때문에 이 판단은 좀더 예리해질 것이라는 것이 바탕이다.

그럴듯 해보지만 사실 개인적으로는 전형적인 공돌이 마인드라고 생각한다. 사회공학적인 생각을 해보자 지난 24시간동안 수(십)만개의 댓글중에 스팸글이 단 하나 올라왔다면 이는 사실 무시해도 상관없는 문제이다. (물론 1개 올라온 댓글이 스팸이라면 조금 슬프다-ㅅ-) 피해는 아주 미미할 것이고 청결 공포증을 가져야 할 필요는 없다.

스팸이 스팸이라고 불리는 이유는 단순히 댓글에 sexy 라는 단어가 있기 때문이 아니라 그 무작위적인 전파성과 양에 근거하는 바가 더 크다. 그래서 이런 필터를 만드는 걸 생각해 볼 수 있다. (물론 앞의 글에 적었듯이 필터 하나 하나는 점수를 줄뿐 하나의 필터를 위반하였다고 해서 바로 스팸이라고 판단하는 것이 아니다. 베이지언 알고리즘도 하나의 필터로 사용할 수 있다.)

Case 1)
지난 24시간에 같은 내용의 글이 - 이를테면 같은 내용의 글이 이미 x개 만큼 존재한다면 스팸일 확률이 높다.


그러나 24시간동안 수십만개의 댓글들이 등록된다면 수십만개의 글을 비교하는 시간이 만만찮다.
- >=와 !=를 판단하는데 있어 굳이 글 자체를 저장할 필요는 없다.  그래서 실제로는 글의 HashCode를 비교한다. 수십K의 글을 4byte 혹은 8byte로 저장할 수 있기 때문에 굳이 DB에 저장할 필요도 없다. 25만개의 댓글을 4byte int로 표현하면 고작 1M 정도이다. 4byte가 42억개의 경우의 수가 있지만 전혀 다른 글이 같은 숫자로 변할 수도 있는 가능성을 생각할 수도 잇지만 이는 단지 판단에 있어서의 하나의 가중치일뿐임을 기억하자.


그러나 스팸글을 올릴때 중간에 스페이스나 특수기호를 사용하여 이부분을 피하려고 시도한다.
-> 스팸글을 올릴때마다 스페이스나 특수기호를 바꿔야 한다면 이는 어느정도는 성공한것이다. 스패머를 귀찮게 만들면 이미어느정도 의미가 있다. 기대되는 이익보다 더욱 많은 수고를 해야 한다면 스패머는 줄 것이기 때문이다. 그건 그거고 앞의 HashCode를 저장할때 특수기호나 화이트 스페이스 문자등은 제거하여 저장하고 비교한다면 좀더 성공확률이 높다. 스패머는 단순히 점 하나 더 찍어주는 걸로 이런 비교를 피해갈 수 없기 때문에 문장 자체를 다시 만드는 수고를 해야 한다. 순서만 바꾼다면 맞춤법 필터에 걸릴 가능성이 높아진다.

만약에 저장공간에 좀더 여유가 있다면 화이트 스페이스나 특수문자들을 사용해서 정해진 규칙에 따라 몇개의 그룹으로 나누고 각각의 숫자들을 저장하는 방법을 쓸 수도 있다. 용량이 다소 늘어나겠지만 이제 스패머는 문장 자체를 재 구성해야 한다.

그러나 예상치못한 사용자가 생길수도 잇다.
-> 단순히 글 내용으로만 판단한다면 짧은 내용의 글 - 특히 유행어일 경우는 -들은 겹칠 확률이 아주 높다. 하지만 꼭 글만 가지고 판단한 필요는 없다. 사용자로부터 알아낼 수 있는 정보는 글 내용 말고도 아이피나 Header에 포함되어 있는 브라우저, 시스템이 남기는 쿠키 등 아주 많다. 특히 프로그램을 이용한 스팸일 경우 header나 ip는 중요한 판단자료가 된다.


Case 2)
한명의 사용자가 지나차게 많은 글을 올린다면 이는 스팸일 확률이 높다.

그러나 회원 등록제가 아니다.
-> 요즘 댓글들은 대부분 회원제든 아니든 인증을 거치게 되어 있기 때문에 현재 큰 장애요소는 아니다. 그보다 한탕주의 처럼 새로운 회원 가입하고 한동안 스팸글을 올리고 잠적하는 것이 더 일반적이다. 이 때 중요하게 판단 할 수 있는 사실은 시간이다. 스패머가(혹은 스팸프로그램도 마찬가지) 한시간에 한개씩 스팸글을 올리지는 않을 것이다. 출퇴근 시간에 지하철 입구역 앞에서 집중적으로 광고 전단지를 돌리듯이 스팸은 특정 시간에 집중되어 있다. 짧은 시간에 집중으로 올라오는 아이피나 사용자가 있다면 스팸일 확률이 높다. 회원제라면 가입일수에 맞춰 log곡선으로 글의 허용량을 늘리는 방법을 생각해 볼수도 있다.

그러나 열성 사용자가 화를 낸다.
-> 서비스에 있어서 열성적인 사용자는 아주 중요하긴 하지만 그 수는 극히 적으므로 등록하여 이들은 추가적인 가중치를 부여하거나 제외 할수도 있다.



이렇게 사회공학을 접근하는 필터는 생각할 수 있는 여지가 아주 많이 있고 만들기도 그닥 어렵지 않기 때문에 실용적이다.

'Framework > 스팸 필터링' 카테고리의 다른 글

휴리스틱 알고리즘 + 복합 알고리즘  (0) 2009.03.12
구현  (0) 2009.01.12
판단의 기본 아이디어  (0) 2009.01.12
개요  (0) 2009.01.09
Posted by bleujin
IT 이야기2009. 3. 16. 05:06

방법론에서는 How가 중요하다. 어떤 분석방법을 사용해서 어떤 툴을 사용해서 설계를 하고 어떤 표준으로 코딩을 하고 등등.. 하지만 로버트의 말은 새겨들을 필요가 있다.  

   "많은 프로젝트의 성공과 실패는 어떻게 수행하는가보다 누가 수행하는가에 따라 결정된다.
    - 로버트 L 글라스, 소프트웨어 공학의 사실과 오해"


짧게 말해 How보다는 Who라는 사실이다. 성공한 프로젝트의 How를 흉내내는건 그래서 별로 효과적이지 못하다. NASA의 프로젝트에 감명을 받았다면 NASA의 방법론을 베낄게 아니라 NASA의 사람을 데려와야 한다. 그럼에도 왜 이렇게 많은 방법론 책들이 있을까 ? 그 의문에 대해 디마르코는 다음과 같이 말했다. 

    "사람들이 업무의 인간적인 측면보다 기술적인 측면에 주로 매달리는 가장 큰 이유는
     기술적인 부분이 더욱 중요하기 때문이 아니라 거기에 매달리는 것이 훨씬 더 쉽기 떄문이다. 
     -톰 디마르코, 피플웨어


기술적인 측면에 매진한 결과는 이렇다. 

     "대부분의 프로젝트는 기술이 아니라 인적 자원과 프로젝트 관리의 문제로 실패한다.
      - R. Thomsett
     "업무에서 발생하는 문제들은 대부분 기본적으로 기술의 문제가 아니라 조직사회학의 문제다. 
      -톰 디마르코, 피플웨어


사실 좋은 프로그래머를 찾는 것은 

      "생산성에 대해 어떤 사람들은 다른 사람들보다 5배나 우수하고
       디버깅에서는 어떤 사람들은 다른 사람들보다 28배나 우수하다.
       - 로버트 L 글라스, 소프트웨어 컨플릭트 2.0


에 의하면 타당하다.  5배가 우수한 사람이라도 5배의 비용이 들지는 않을 것이기 때문이다. 혹시 2배 뛰어난 사람을 구하진 못해도 2배 더 일하게 하는 건 어떨까 라고 생각하는 관리자가 있다면 

      "초과근무 시간 증가는 생상성 감소 기법이다. 스트레스를 받는 사람들은 머리가 빨리 돌아가지 않는 법이다. 
       - 톰 디마르코, 피플웨어

     "아홉 명의 여자가 투입된다고 해서 아기를 한 달만에 낳을 수는 없다.
     - 프레드릭 브룩스, Man-Month의 신화

라는 유명한 격언이 있으니 굳이 다시 해볼 필요는 없다. 사실 대부분의 프로그래머는 이미 주 70-80시간 정도를 일하고 있으니 초과근무가 해법이 아닌것은 분명하다.


이러한 관리자에게 해주고 싶은 말은 앞의 디마리코가 이미 언급하였다. 

     "관리자가 진정해야 하는 일은 사람들에게 일을 시키는 것이 아니라
      그들이 일에 전념할 수 있는 환경을 만들어 주는 것이다.
      - 톰 디마르코, 피플웨어


단순히 프로그래머에게 일하는 시간의 양이 중요하지 않다는 얘기는

      "프로그래밍에서는 평균적인 수준의 노동력을 유지하는 것보다
       영감이 샘물처럼 솟아나는 소중한 순간을 놓치지 않는 것이 중요하다. 
       - 임백준

가 언급하였다.


경력이 비슷한 프로그래머간에도 품질과 생산성의 격차가 생기는 이유는 공장에서 찍어내는 KS가 찍힌 양산품이 아니기 때문이다. 

       "컴퓨터 사이언스를 가르치는 교육이 어떤 사람을 전문적인 프로그래머로 만들지 못하는 것은,
        붓질과 채색방법을 가르치는 교육이 어떤 사람을 전문적인 화가로 만들지 못하는 것과 같다.
        - 에릭레이먼드, 해커와 화가 중



그래서 일찍이 제럴드 와인버그는 이런 말을 했다.

      "모든 프로젝트에는 세가지 문제가 있다. 사람 사람 사람 .
      제럴드 와인버그, 컴퓨터 프로그래밍 심리학

'IT 이야기' 카테고리의 다른 글

Nice game  (0) 2009.03.25
멘탈 2  (0) 2009.03.23
프로젝트의 예측  (0) 2009.03.12
Scott Adams  (0) 2009.03.11
사람이 읽는 코드  (0) 2009.03.11
Posted by bleujin
Book2009. 3. 14. 09:49

리팩토링 데이터베이스: 진화적 데이터베이스 디자인
Scott W. Ambler/정원혁 | 위키북스 | 번역서 | 2007-06-29
종합평점 
도서수준 
2007년 기술 생산성부문에서 졸트상을 받은 책으로 올해 초 원서와 여름에 번역서 2번을 읽었는데 감상이 다릅니다.
 
처음 읽었을때는 아주 황당했던 책으로 이봐~ 이런걸 누가 몰라. 초보적인 SQL이구만
(지금 생각해보니 파울러의 리팩토링을 처음 읽었을때도 같은 생각을 했습니다. )
몇장씩 챕터별로 나누어져 있었기 때문에 매우 빠르게 읽을수 있어 금방 읽고 치워버렸습니다. 
그런데 얼마전에 기존에 전혀 접해보지 않은 분야의 모델링을 하게 되었을 때
초기에 좋은 모델링을 하기가 얼마나 어려운가를 생각하게 되었습니다.
 
흔한 현실의 변명처럼 기초자료는 충분하지 못하고 참조가능한 예제도 없고
게다가 모델러 스스로가 익숙하지 못한 도메인에서 일할때 
기존의 지식과 규칙들은 그리 크게 도움이 되지 못하며
전혀 모르는 도메인 외계어와 의미를 알수없는 약어, 데이타들이 수백개의 표들로 이루어진 정보로 변해야 할때,
충분할만큼 완전하며 미리 예측가능한 ERD를 만드는건 매우 어려운 상황에서 무심코 다시 이 책을 읽게 되었을때
이책의 논리는 아주 합리적이며 이성적으로 보였습니다.
 
이전에 변수 이름 바꾸기 같은 리팩토링의 방법을 몰라서 안한게 아닌것처럼
진화적 데이타베이스의 SQL을 몰라서 안한게 아니었지만 책을 읽고 조금 더 많은 생각을 하게되었습니다.
 
 
하지만 리팩토링을 강력히 주장하는 XP의 약점중의 하나로 자주 지목되는
수행하는 사람의 질적인 문제로 다시 돌아가지 않을 수 없습니다. 
유명한 미국의 물리학자인 파인만은 어떤 교육방법이 기존 방법보다 더 낫다는 걸 증명하려면
실험이 새로운 교육방법을 주장하는 교사가 아닌 기존의 평범한 교사가 수행하여 비교해야 한다고 말한곤 했습니다.
 
왜냐하면 이미 잘 알고 있고 새로운 방법을 충분히 이해하며 기존 방법의 장단점을 분석하여 이해하는 교사는
어떤 방법을 쓰느냐와는 상관없이 기존방법에 비해서 비교실험에서 우위를 나타내는게 당연하므로
기존의 평범하고 대다수를 차지하는 평범한 교사가 잘 이해하지 못하는 새로운 방법으로 시도했을때 비교우위가 나타나야지
그 교육방법이 더 좋은 방법으로 말할 수 있기 때문입니다.
 이 경우 사실은 교사라는 사람의 변수에 의해 나타난 효과가,
x방법이라는 변수가 효과에 영향을 미친걸로 오해되기 쉽습니다.
 
이 책을 읽고 "DB모델도 유연하게 변경할수 있다"라고 생각하는게 아니라
"위 책에서 보듯이 DB모델은 언제든 수정가능하기 때문에 괜찮을 거다"라는 변명거리로 사용된다면,
대다수의 소규모의 팀으로 제작되는 소프트웨어에서
해당 프로젝트의 재앙에 이 책은 아마 기름을 뿌리는 효과가 될지도 모릅니다. 
 
다소 허망하지만
좋은 프로그래가 어떤 기법을 쓰느냐와 상관없이 좋은 프로그램을 만드는 것과 같이
좋은 모델러는 어떤 기법을 쓰느냐와 상관없이 좋은 모델을 만듭니다.
 
이 책이 좋은 모델러를 만드는데 기여할수는 있겠지만
책의 방법을 따른다고 "해당 프로젝트의 기간"내에서 좋은 모델이 만들어지지는 않을 것 같습니다.
 
마틴파울러의 리팩토링책이 그러했던 것처럼
단지 우리는 실수를 통해 더 빠른 피드백을 얻고 더 많은 생각을 하게 됨으로써
조금씩이나마 조금 더 나은 모델러로 성장하여 "다음 프로젝트에서는" 조금 더 나은 모델을 만들수 있을것입니다.

'Book' 카테고리의 다른 글

Ajax Design Patterns  (0) 2009.02.10
소프트웨어 컨플릭트 2.0 : 시대를 뛰어넘는 즐거운 논쟁  (0) 2009.02.10
프로그래밍 심리학  (0) 2009.02.10
추천 도서  (0) 2009.01.16
Javascript: The Definitive Guide  (0) 2009.01.11
Posted by bleujin
Framework/Another Lore2009. 3. 13. 09:58



Self Reflectivity
  Boot-strapping
    - 자신이 자신을 실행한다.
    - 논리학, 전산학
 
  프로그램을 만드는 프로그램
 


Meta Congnition

  Meta ?
    - Meta Content : 컨텐트에 대한 컨텐트(ICS의 ActionField)
    - ex : 1000
     <money>1000</money>
        <money unit=“won”>1000</money>

  Meta적인 관점
    - GOF Pattern의 통찰
    - 다양한 컨텍스트에서 공통의 해결책을 가진 공통의 문제


Extreme
  Extreme ?
    - XP(eXtreme Programming : Test를 과도하게, 통합을 과도하게)
    - 갈매기 조나단
    - 숫자 0과 1의 차이, 숫자 1과 2는 ?

  양적인 차이와 질적인 차이
    - 자동차와 비행기
    - 비등점의 물
    - 양적인 차이가 질적인 차이를 만든다


Abstraction

  추상화란 ?
    - 단순화와 강조(피카소, 캐리커쳐)
    - ex) 지도

  Application의 추상화
    - Model
    - Process
    - View

'Framework > Another Lore' 카테고리의 다른 글

와~~~  (0) 2009.04.09
AL - Extreme  (0) 2009.04.04
AL - Code Example  (0) 2009.03.20
AL - 배경  (0) 2009.03.07
Framework - AL(개요)  (0) 2009.03.06
Posted by bleujin

프레임워크를 작성하는 데 있어 가장 필요한 능력은 무엇일까 ? 프레임워크의 사용자는 다른 프로그래머이기 때문에 프레임워크를 작성하는 사람은 아무래도 old한 경력이 좀 된 프로그래머여야 할테니 기본적으로 언어나 툴의 능숙한 사용같은건 제약요소가 되지 않는다.

개인적으로 가장 중요한 능력은 "눈" 이라고 생각한다. 물론 물리적인 눈을 말하는게 아니라 "관점"의 의미를 가지는 눈이다. 대부분의 프로그램은 스파게티 코드로 얽혀있고 꼬아져 있다. 와인버그는 일찌기 "건물을 짓는 사람들이 프로그래머가 소프트웨어를 제작하는 방식으로 건물을 짓는다면, 창공에서 날아온 첫번째 딱따구리는 전 인류의 문명을 파괴할 것이다"라고 말한바가 있다. (http://cjhgogox.egloos.com/1757251 프로그래머가 비행기를 만든다면..:) 불안정한 소프트웨어에서 거미줄처럼 얽혀 있는 이런 막연한 공간에서 존재하지 않았던 공통점을 발견하는 것은 프레임워크 개발에 가장 필요한 능력임과 동시에 가장 어려운 능력이기도 하다.

예컨데 여기의 Validation Framework는 예전에 루비의 선언적 프레임워크란 글을 읽고 반나절만에 작성하였다. 이전의 명령형이나 로직으로 처리하던 이전의 관점에 머물러 있을때는 보이지 않았었지만 새로운 관점을 익히니 문제가 떠올랐고 해결책은 더할나위없이 쉬웠다.

Code Analyzer같은 매트릭스 프로그램을 몇개 돌려보면 코드 중복을 찾는건 그다지 어려운 일이 아니다. 그러나 수십만줄의 코드 사이에서 구조적인 중복을 발견하는 것은 쉬운일이 아니다. 그나마 이 블로그의 소위 인프라 시스템 프레임워크라고 할 수 있는 DB, Configration, Message 등이 그나마 나은건 문제에 대한 인식이 쉽기 때문이기도 하다. 그 외의 프레임워크는 해결책을 찾기 전에 문제를 찾아야 하고 그 문제의 해결책은 그 문제를 바꾸지 않아야 한다. 그래서 더 어렵다.

다만 도움이 될 수 있는 한가지 말을 하자면 한사람이 2가지 관점을 가지는 것보다 다른 관점을 가진 두사람이 만나 협력하는 것이 좀더 쉽다.


'Framework > 아키텍쳐 일반' 카테고리의 다른 글

Self-Signed Java Applets  (0) 2009.06.01
여섯번째 계 - Encapsulation  (0) 2009.04.14
Here is Dragon  (0) 2009.03.12
프로그래밍 패러다임  (0) 2009.03.12
아키텍쳐 패턴 - Broker 패턴  (0) 2009.03.12
Posted by bleujin
Framework/Database2009. 3. 13. 07:31

키셋이나 다이나믹 커서는 최소한 connectionless 웹환경이라면 거의 쓰이지 않기 때문에 잘 보이지 않지만 동시성이 매우 중요한 일부에서는 고려될수 있는 커서 방식이다.



  1. WAS는 쿼리 실행을 요청한다.  
  2. DB는 쿼리의 "모든" 결과의 "Key 셋(unique index)"을 메모리에 snapshot 형태로 만든다. 모든 데이타의 load를 완료하면 Client의 신호를 기다린다
  3. Client(WAS)는 Fetch(rs.next())를 요청한다.
  4. DB는 cursor 정보를 유지하면서 current Key의 정보로 DB를 lookup해서 row의 정보를 알아낸다
  5. 알아낸 row를 Client에게 보낸다. 
  6. 패킷(1개의 row만 들어있음)을 받았다는 confirm 메시지를 보낸다.
  3,4,5,6의 동작을 9번 더 반복한다

KeySet은 Static과 상당히 비슷하지만( 실제 메모리에 로드하는 것은 전체 결과셋이 아니라 결과셋의 Key만을 메모리에 올린다.(만약 테이블에 unique index가 없다면 이 방식은 실행할 수 없으므로 이 시점에서 자동으로 static cursor 모드로 변경된다.) 메모리에는 Key들의 정보만 있기 때문에 매 Fetch 시마다 데이타베이스 lookup을 해서(해당 키로 DB를 다시 Access read한다.) 해당 Row의 정보를 리턴한다. (물론 select 컬럼에 key가 아닌 컬럼이 있을 경우이다. 만약 select 컬럼이 모두 key의 부분이라면 역시 static mode와 동일하다.)

여기서 맨처음의 예제처럼 하나의 테이블에서라면 문제없겠지만 만약 쿼리가 2개이상의 테이블을 조인하는 경우라면 어떻게 될까? 조인된 테이블은 실제 존재하는 것이 아니므로 Key 정보를 유지할수 없다. 여기서의 구현은 사실 DB와 드라이버 마다 틀리기는 하지만 대부분은 이경우 keyset이 작동하지 않는다.

키셋을 사용한다면 가장 큰 이유는 Key정보만을 메모리에 유지하고 나머지 Data는 매번 새로 read를 하기 때문에 다른 세션에서 수정한 값을 실시간으로 읽을 수 있는 장점이 있다. 사실 JDBC의 sensitive 방식은 바로 이 keyset을 뜻한다. 다른 Session의 update에 sensitive 하기 때문에 붙여진 이름이다. 하지만 실제로 돌려보면 이 방식을 지원하는 JDBC 드라이버는 많지 않고 그 일부도 제각각 특정 조건을 만족해야만 동작하는 경우가 많다.(이를테면 단일테이블 억세스일때만..) 그리고 만약 사용자 업데이트가 키 컬럼을 수정하였다면 역시 Keyset은 제대로 동작하지 않는다.


단점은
  - WAS에서 필요한 양과 상관없이 해당 쿼리의 모든 Key셋 데이타를 읽어야 한다.
  - DB에서 읽은 모든 Row를 KeySet을 Snapshot 형태로 저장해야 하기 때문에 Static보다는 적지만 공유 자원인 DB 메모리는 여전히 많이 소모된다.
   - 매 fetch마다 DB Read가 일어난다.
   - 동기적으로 작동하기 때문에 Client의 fetch 요구시마다 row단위의 데이타 전송이 이루어진다.(패킷의 크기가 작다.)
   - cursor를 해당 프로그램이 종료될때까지(DB close 할때까지) 유지해야 하고 더 많은 정보를 관리해야 한다.
   - 첫번째 Row가 Client에 전송되는 시간도 Static 보다는 나을지라도 여전히 느리다.
   - RR의 동시성 문제로 다중 사용자 환경에서 느리다.

장점 
   - Client가 필요한 양만 네트워크에 전송한다. 
   - Repeatable Read가 가능하다.
   - 일부만 억세스한다면 세번째 단점에도 불구하고 Static 보다는 더 빠르다.







마지막으로 Dynamic 커서 방식을 보자. 위 그림을 보면 먼가 이상하다고 생각하겠지만 이는 실수가 아니다. 

  1. WAS는 쿼리 실행을 요청한다.  
  2-3 DB는 쿼리 결과의 메모리에 로드한후 첫번째 Row의 정보를 WAS에게 보낸다. 
  WAS는 fetch를 요청할때마다 해당 SQL은 다시 실행되서 다시 메모리에 올린후 cursor 정보대로 x번째 로우를 보내준다.


몰랐던 사람에게는 당황스럽겠지만 Dynamic 커서로 실행되면 rs.next()때마다 매번 쿼리가 다시 실행된다. 2번 load Data때 전체를 올리느냐 혹은 일부만 올리는가는 역시 DB 마다 구현이 다르지만 보통은 전체를 다시 올린다. 이런 제정신이 아닌듯해 보이는 짓을 하는 이유는 Pantom을 read하기 위해서이다. 앞의 Keyset 커서는 쿼리가 실행된 이후 다른 사용자가 업데이트 한 정보는 볼수 있지만 사용자가 새로이 insert한 row(일명 pantom row)는 보지 못한다. 다이나믹 커서는 매 패치시마다 쿼리가 다시 실행되기 때문에 사용자가 insert한 정보를 억세스 가능하게 한다.

사실 만약 당신이 DB 전문가가 아니라면 Dynamic 커서는 쓸 필요도 없고 써서도 안된다. Dynamic Cursor가 사용하는 잠금 수준은 Serializable 이고 이는 극히 제한적이며 특수한 용도로 사용되며 이 모드는 워낙 DB마다 구현이 천차만별이기 때문에 전용 드라이버의 API를 사용하지 않으면 JDBC API로 이 cursor 방식을 실행시키는 방법도 없다.




여러번 강조했다시피 단순히 설정한다고 해당 커서 모드로 동작하는게 아니다. 위에서 말한 것 말고도 MSSQL의 경우 Select문이 트리거가 있는 테이블과 트리거가 없는 테이블을 조인하면 커서가 static 커서로 변환된다. Forward Only 커서가 읽기 전용이 아니라면 Dynamic 커서로 변환된다. 원격 테이블을 참조하는 분산쿼리라면 커서는 Keyset 방식으로 바뀐다 등등 수많은 예외 사항이 존재한다.

대부분의 프로그래머가 자신도 모르게 사용하는 커서 수준은 Forward Only, Static, Keyset 정도이다. 현재 프로그래밍에서 사용하는 커서 모드를 모른다면 몇가지 확인 방법이 있다. 

  - 첫째 rs.first(), rs.absolute(), rs.last() 같은 메소드가 정상작동한다면 static 아니면 keyset이다. Foward Only는 비동기적으로 작동하기 때문에 위 메소드의 커서이동이 불가능하고 Dynamic는 멤버 row가 고정되어 있지 않기 때문에 fetch absolute가 지원되지 않는다. 커서 용어로 Scroll이 가능한 커서는 static과 keyset이다.
  - 둘째 SQL에 group by 나 max 혹은 union, minus 같은 집계함수나 구문을 사용하였다면 static 이다. 집합의 레벨에 변형이 있다면 Forward Only로 작동할 수 없다. 
  - 셋째 구문에 Order by를 사용했다면 그리고 Where 구문을 위해 인덱스가 사용되었다면 static일 확률이 높다. 정렬정보를 유지하기 위해 Set정보를 유지해야 하기 때문이다. 임시 테이블을 만들 필요가 있는 Select문은 암시적으로 static 커서로 변환되는 경우가 많다.
  - 넷째 static과 keyset 모두 가능성이 있다면 아마도 static 일 것이다. keyset은 잘못 사용했을때 다중사용자 환경에서 잠금으로 인한 속도저하가 있기 때문에 대부분의 Driver는 static을 선택한다.

사실 몇가지 더 있겠지만 원리를 이해하였다면 쿼리만 보고도 이게 어떤 커서로 실행될 것인지 짐작할 수 있다. 단순하게 말하자면 아주 초보적이고, 하나의 테이블만을 대상으로 하고, 집계함수나 Order by 등의 구문을 쓰지 않으면 Forward Only로 동작할 확률이 높고 그 밖의 경우는 대부분 static 으로 동작한다고 해도 거의 틀리지 않는다.

사실 여기까지 소개한 Forward Only나 Static 모두 심각한 단점이 있기 때문에 - 라기 보단 대부분 적절히 피할 방법을 알지 못하기 때문에 - 어느 모드든 그리 좋아 보이지는 않는다. 그리고 이 말은 사실 대부분의 환경에서 심각한 문제를 겪고 있다는 뜻이기도 하다. 만약 정확하게 커서에 대한 지식을 쌓고 적절하게 프로그래밍 한다면 DB 서버는 다중환경에 훨씬 더 최적화 되어 있기 때문에 DB 사이즈와 상관없이 같은 부하에서 웹서버가 다운되면 다운됐지 DB 서버가 다운되는 일은 결코 없다. 


다음글에는 오라클과 MSSQL의 차이점과 오라클과 MSSQL은 이런 문제를 어떻게 피해가는가에 대하여...

'Framework > Database' 카테고리의 다른 글

프로시저 vs SQL  (0) 2009.03.23
Framework - 커서(Anonymous)  (0) 2009.03.19
Framework (Servant)  (0) 2009.03.12
Framework (DBController)  (0) 2009.03.12
Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Posted by bleujin
Database 일반2009. 3. 12. 22:21


select ...
from a, b
where a.col1 = b.col1
   and b.col2 like 'abc%'
   and a.col2 in (select col2 from c where condition)

라는 SQL이 있다고 할때 SQL의 구조를 바꾸지 않고 in절의 sub쿼리가 항상 먼저 실행되도록 만들어라. 최소 3가지 이상의 방법을 고민해 보고 장단점을 생각해보자.


특정 테이블을 명시하지 않은 것은 위의 테이블이 어떤 테이블이건 그리고 어떤 조인을 사용하는지에 상관없어야 하기 때문이다. 보통의 경우 in subquery는 조인후에 실행된다. (항상 그런건 아니고 단순히 확률을 말함) 만약 위 쿼리문이 Hash 로 풀린다면 먼저 in subquery가 실행되는것과 a, b 조인후에 in subquery가 실행되는 것은 하늘과 땅차이다. 물론 NL이나 SM도 많은 차이를 가진다. 문제는 위 쿼리가 어떤 실행계획을 사용하든지 상관없어야 하고 또 이전 조인 방법을 바꾸지 않아야 하기 때문에

select ...
from c, a, b
where ....

와 같은 식으로 바꿔서는 안된다는 점이다.

'Database 일반' 카테고리의 다른 글

Database - Oracle Hint  (0) 2009.03.07
Database - Plan 이해  (0) 2009.03.07
Database Quiz - count  (0) 2009.02.13
Database Quiz - 함수의 활용  (0) 2009.02.07
Database Quiz - 실행계획 유도  (0) 2009.02.07
Posted by bleujin
Framework/Database2009. 3. 12. 21:10


앞에 나온바와 같이 Query의 실행이라는 기본목적외에도 현실에는 여러가지 일들이 있다. 그래서 각각의 일들을 해주는 Servant(하인)들이  있다. 단지 본래의 일(Query의 실행)에 영향을 주어서는 안되기 때문에 별도의 쓰레드로 관리된다. 모든 쿼리는 실행이 완료되면 IQueryable객체와 걸린시간정보를 묶은 AfterTask를 Channel에 넣어두고 종료된다. ServantChannel에 AfterTask를 등록한 시점에서 DC는 더이상 관여하지 않는다.

여러개의 Servant는 Chain 형태로 연결되어 있다. 체인의 가장 선두가 Channel에 새로운 AtferTask가 등록되면 가져와서 isDealWith로 자신이 할일인지 판단하여 자신의 작업을 한후 nextServant에게 Task를 전달해준다. Servant는 별도의 Thread로 등록되어 있기 때문에 dc의 작업에 아무런 영향도 끼치지 않는다.


FileServant :
설정된 시간보다 느린 모든 타입(Command, Procedure, Batch 등등)의 쿼리를 잡아서 특정 파일에 기록을 남긴다.

NoneServant :
아무 일도 하지 않는다. Null Object Pattern.


StdOutServant :
특정 타입의 쿼리가 실행될때 걸린 시간등의 정보와 함께 화면에 출력한다.

TraceOfLateProcServant :
설정된 시간보다 느린 모든 타입(Command, Procedure, Batch 등등)의 쿼리를 잡아서 특정 테이블에 기록을 남긴다. 로그파일보다 Table에 기록을 남기면 좀 더 편하게 문제점을 분석할 수 있다.



다이어 그램이 Thread때문에 복잡해지긴 했지만 그닥 어렵지도 않고 특별한 의미를 가진 부분도 아니다.
단 Servant는 프레임워크의 확장 포인트의 일부이고 시스템을 새로 시작하지 않아도 런타임중에 새로운 Servant의 등록과 삭제가 가능하기때문에 여러가지로 응용이 가능하다. 이를테면 XML 파일에 설정된 쿼리가 실행될때 특정 사용자에게 메일을 보내도록 하는 Servant도 있다. 화면 모니터링을 통해 실시간으로 문제 쿼리의 유무를 감시하는데 사용하기도 한다.



'Framework > Database' 카테고리의 다른 글

Framework - 커서(Anonymous)  (0) 2009.03.19
Framework - 커서(Keyset, Dynamic)  (0) 2009.03.13
Framework (DBController)  (0) 2009.03.12
Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Framework (Handler)  (0) 2009.03.08
Posted by bleujin
Framework/Database2009. 3. 12. 17:21




파란색을 실험실로 간주했을때 dc는 실험실의 연결창구 역할을 하게 된다. Client는 기본적으로는 DBController(이하 dc)를 통해서 메시지 교환을 한다. 

DBController는 Facade 패턴의 예중 하나로 Facade(건물의정면) 패턴은 복잡한 서브 시스템에 통일된 인터페이스를 제공함으로써 복잡한 API를 단순화 시켜준다. 시스템을 서브 시스템 단위로 나누어 구성하는 것은 시스템의 복잡도를 낮춰주지만, 동시에 서브 시스템 사이에서의 통신 부하와 결합도가 증가하게 된다. 이러한 서브 시스템 사이의 의존도를 낮추고, 서브 시스템의 사용자 입장에서 사용하기 편리한 인터페이스를 제공하고자 하는 것이 facade 객체이다.

Facade 객체는 실생활에서의 고객 서비스 센터와 유사하다. 가령, 어떤 상품을 구매하는 과정에서 문제가 생겼다고 가정할 때, 고객이 문제의 성격에 따라 해당 부서에 직접 연락하는 것이 아니라 고객 서비스 센터를 통하는 것은 Facade 패턴에 대한 좋은 유추 사례가 될 수 있다.

여기서 DBController Facade 역할이 하는 일은 무엇일까 ?

첫째 복잡한 것을 단순하게 보여준다. 실험실 내부에서 작동하고 있는 내부에서 작동하고 있는 많은 클래스들의 관계나 사용법을 의식하지 않도록 해 준다. Java 접근자는 구조적인 이유로 public을 쓸수밖에 없는 경우가 종종 있는데 그중에 일부만 - 즉 개념적인 published 메소드 - 만을 노출시킴으로써 보이는 API를 적게 해 주고 단순하게 해 준다.

둘째 실험실의 컴포넌트로부터 클라이언트를 격리하여 실험실과 클라이언트 사이의 의존성을 낮춘다. 이런 캡슐화는 클라이언트와 서브시스템의 생존 기간이 다르기 때문에 필수적이다. Facade 패턴을 사용한다고 해도, 필요한 경우 실험실의 클래스에 직접 접근할 수도 있다.(이러한 것을 터널링이라고 부른다.) 즉, 일반화 정도(generality)와 개발의 편의성 사이에서의 적당한 합의점을 찾아야 한다.

Facade 패턴의 일반적인 특징에 추가로 위 DBController은 2가지를 더하고 있다.(SRP에는 위반되지만 ..) 첫번째는 실험실에서의 checked exception을 runtime exception으로 바꾼다. exception framework에서 말한대로 첫째로 대부분의 SQLException을 client에서는 잡아서 현명한 처리를 하지 않으며 둘째로 기껏해야 로그 기록하는 정도라면 본래 코드를 작성하는데 오히려 방해가 되기 때문이다. 이런 예외 처리가 현재 상황에서 적합하지 않다면 DC를 상속받아 해당 메소드를 재정의 하여야 한다.(실제 필요한적이 한번도 없었기 때문에 미리 만들어 두지 않았다.)

다른 한가지 역할은 Servant Filter 등록을 관리한다. 비실험실인 client는 현실의 요구사항이 있다. 이를테면 특정 쿼리의 실행을 모니터 하고 싶을 수도 있다.(느린 쿼리, 배치형 쿼리같은 특정 타입, 특정 테이블을 사용하는 쿼리 등등) 그리고 이 부분은 모두 필터로 관리되어 런타임중에 추가 삭제가 가능해야 한다. (나중에 다시 언급)


보통의 경우 Facade 객체는 Singleton 패턴으로 사용되는 경우가 많지만 DC의 경우 실제 커넥션 풀링등의 리소스 관리는 DBManager에게 맡기고 있으므로 배치 처리용으로 Servant가 없는 별도의 BatchDBController를 사용해도 되고 시간이 오래 걸리는 쿼리의 경우 비동기적으로 작동하는 AsyncDBController를 사용해도 된다.

'Framework > Database' 카테고리의 다른 글

Framework - 커서(Keyset, Dynamic)  (0) 2009.03.13
Framework (Servant)  (0) 2009.03.12
Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Framework (Handler)  (0) 2009.03.08
Framework (Rows)  (0) 2009.03.07
Posted by bleujin