네트워크 프로그램을 개발할 일은 아주 가끔 있지만, 대부분 원격의 서버와 통신하는 경우가 대부분이라서 

테스트하기도 힘들고, 운영 중이라면 언제 어떤 문제가 발생할지 예측할 수가 없다.

그래서, 각 오류별로 발생하는 문제와 이 문제의 원인을 미리 파악하고 있으면 신속한 대응이 가능하다.


네트워크 프로그램

간단하게 구성하자면 원격에서 클라이언트와 서버가 통신하는 것이라고 할 수 있다.

서버는 데몬 형태로 항상 떠 있으면서 클라이언트의 요청이나 데이터를 받고, 그에 대한 응답을 주는 역할이고,

클라이언트는 서버와 통신할 필요가 있을 경우에 객체, 프로세스, 스레드 등을 생성해서 서버에 요청을 보내고 응답을 받거나, 데이터를 보내고 ACK를 받은 후에 종료되는 형태이다.

웹을 예로 들면, 웹브라우저가 클라이언트가 되고 웹서버나 WAS는 서버의 역할을 한다.


네트워크 통신 과정

1. 연결

   클라이언트와 서버간에 커넥션을 생성하는 과정이다.

   클라이언트가 서버에 연결을 요청하고, 서버가 연결을 수락한다는 답을 보내면 커넥션 생성이 완료된다.

2. 데이터 송수신

   커넥션이 생성된 후에는 클라이언트가 서버에 요청이나 데이터를 전송한다.

   서버는 수신한 요청에 대한 응답을 생성해서 클라이언트로 전송하거나, 단순히 ACK를 전송한다.

   클라이언트가 정상적으로 응답이나 ACK를 정상적으로 수신했다고 판단되면 커넥션을 종료하거나, 다음 요청을 보내는 식의 작업이 이어진다.


동기/비동기 방식

(이 포스트와의 큰 관련이 없어 보이지만, 헷갈려 하는 분들이 많아 간단하게 언급하려고 한다)

- 동기

클라이언트가 서버에 요청을 보내고 연결이 유지된 상태에서 응답을 기다리다가, 서버에서 응답을 받은 후에 커넥션을 종료하는 형태이다.

한 개의 연결로 요청 및 응답이 모두 처리되는 형태로, 가장 일반적인 형태이다.

- 비동기

A시스템의 클라이언트가 B시스템의 서버에 요청을 보내면 그 내용을 저장한 후에 잘 받았다는 의미의 ACK만을 클라이언트에 전송하고 커넥션이 종료된다.

그 이후에, B시스템에 저장된 요청에 대한 처리가 끝나면 클라이언트를 생성하여 A시스템의 서버에 접속해 응답을 보내고, A시스템의 서버는 잘 받았다는 ACK를 전송하고 커넥션이 종료된다.

결과적으로 A와 B 시스템에 각각 클라이언트/서버가 모두 존재해야 하고, A시스템이 받은 응답이 어떤 요청에 대한 것인지를 판단하기 위해 추가로 ID값을 요청, 응답, ACK에 달고 다녀야 한다.

비동기는 구현이나 관리가 복잡해지는 단점이 있어, 동기식으로 처리할 경우 응답을 기다리다가 타임아웃이 발생할 정도로 오래 걸리는 경우에 주로 사용한다.


네트워크 프로그램 오류의 종류

- 커넥션 타임아웃

클라이언트에서 서버로 커넥션 요청을 보냈는데 지정된 시간동안 그에 대한 응답이 없을 경우에 발생한다.

(참고로, 보통 API에서 커넥션 타임아웃에 대한 값을 지정할 수 있으니, 프로그램이나 서버 환경에 맞게 적절히 세팅해 사용한다.)

거의 방화벽이 막혀 있어서 발생한다.

-> 보통 2개의 방화벽이 있을 수 있는데, 클라이언트단에 있는 방화벽에 클라이언트에서 서버가 가는 방화벽이 열려 있는지 확인하고, 서버단에 있는 방화벽에 클라이언트로부터 서버로 들어오는 방화벽이 열려 있는지 확인해야 한다.

네트워크 전문가가 아니라서 잘은 모르지만, 대부분 방화벽은 금지된 통로로 오가는 패킷들은 그냥 받아서 없애 버리는 것 같다. 그래서, 커넥션 요청을 보내 놓고 마냥 기다리다가 커넥션 타임아웃이 발생하는 것으로 보인다.

- Read Timeout

이 오류가 발생하는 것은 이미 커넥션은 생성되어 있다는 의미이다.

커넥션이 생성된 후에 클라이언트가 서버로 요청을 보내고 응답을 기다리다가 타임아웃이 발생한 것이다. 즉, 서버가 지정된 시간 안에 응답을 주지 않았다는 것이다.

클라이언트의 문제일 가능성은 적고, 서버에 문제가 생겨 응답을 주지 못 하는 경우가 많으므로, 서버단 담당자에게 확인을 요청해야 한다.

- Too Many Open Files

파일을 열 때 뿐만 아니라, 소켓을 열 때도 서버의 File Descriptor를 사용한다. 

즉, 소켓도 사용 후에 잘 닫아 줘야 이런 오류가 발생하지 않는다.

일단, 현재 열려 있는 파일과 소켓을 보고 싶으면 lsof나 pfiles 명령을 이용해서 확인한다.

가끔씩 잘 닫아 줘도 이런 문제가 발생하는 경우가 있다.

서버는 WAS의 서블릿을 만들어 두고, HttpClient를 사용해 클라이언트를 만들어 통신한 후에 클라이언트를 종료하도록 구성했다.

그런데, HTTP의 특성상 API로 커넥션을 종료시켜도 한동안 TIME_WAIT 상태로 커넥션이 머물러 있다가 잠시 후에 진짜 커넥션이 종료되는 것이었다. 단시간에 많은 통신을 수행한 경우 이런 TIME_WAIT이 미처 풀리지 않고 많이 쌓여 있다가 더 이상 커넥션을 생성하지 못 해 Too Many Open Files가 발생한다.

이런 경우는 Connection Pool을 사용해 일정한 수의 커넥션들을 생성해 놓고 재사용하도록 하면, 커넥션이 늘어나지 않아 Too Many Open Files를 막을 수 있고, 매번 커넥션을 생성하는 데 소요되는 시간을 절약하여 성능을 향상시킬 수 있다.

Apache HttpClient의 Connection Pool 사용법은 여기를 클릭하면 확인할 수 있다.


AND


가끔 대용량 파일을 열어서 내용을 확인할 일이 생긴다. 주로 로그파일을 열 때 그렇다.

vi
파일을 열 때 가장 많이 사용하는 게 vi인데, 이건 대용량 파일을 열면 로딩할 때 아주 오래 걸리거나 아예 못 여는 경우도 가끔 생긴다.
연다고 해도 메모리를 많이 사용하기 때문에, 운영서버에서 사용하는 경우는 신경이 많이 쓰인다.

more
이 명령은 파일의 편집 기능이 없어, 대용량 파일도 가볍게 띄운다.
단, 기능도 너무 가벼워서 검색을 해도 내가 찾는 내용이 어디에 있는지 확인하기가 어렵다.
그리고, 일단 아래로 내려오면 위의 내용으로 올라갈 수가 없다.

less
more의 가벼움과 vi의 찾기 기능의 장점을 모아서 만들어진 것이 less이다.
이름은 less인데 more보다 기능이 많다니, 아이러니하게 지은 작명 센스가 꽤 괜찮다.
기능적으로도 참 센스있게 만들었다.

아래는 less의 기능
(vi의 command 모드에 있다고 생각하고 아래의 단축키를 눌러 주면 된다)

g : 1라인으로 이동
G : 마지막라인으로 이동
/pattern : 특정 패턴(문자열) 찾기
n : 정방향으로 계속 찾기
N : 역방향으로 계속 찾기
f : 다음 페이지로 이동
b : 이전 페이지로 이동
-N[return] : 라인넘버 표시 토글 (속도 아주 많이 느려짐. 꼭 필요할 때만 켰다가, 바로 꺼야 함)
q : 나가기



AND


HttpClient로 빈번히 connection을 맺었다가, 사용이 끝나면 끊고 하다 보면 더 이상 connection을 열 수 없는 경우가 발생할 수 있다.
그 이유는 connection을 닫는다고 호출을 해도, 실제로는 어느 정도 TIME_WAIT 상태에 있다가 끊어지는데 이런 것들이 많이 쌓여 있으면 File Descriptor가 꽉 찼다는 에러(Too Many Open Files)가 나면서 connection을 맺지 못 하게 된다.
이런 현상을 방지하기 위해서는 Connection을 재사용할 수 있도록 HttpClient에서 제공하는 Connection Pool을 사용하는 것이다.


<사용 라이브러리>
httpclient-4.2.3.jar, httpcore-4.2.2.jar

public class ConnectionManager {

private static PoolingClientConnectionManager connectionManager = null;
public static synchronized HttpClient getHttpClient() {
if (connectionManager == null) {
connectionManager = new PoolingClientConnectionManager();
connectionManager.setMaxTotal(50);
connectionManager.setDefaultMaxPerRoute(20);
}

return new DefaultHttpClient(connectionManager);
}

public static void abort(HttpRequestBase httpRequest) {
if (httpRequest != null) {
try {
httpRequest.abort();
} catch (Exception e) {}
}
}

public static void release(HttpResponse response) {
if (response != null && response.getEntity() != null)
EntityUtils.consumeQuietly(response.getEntity());
}
}


요런 식으로, static으로 PoolingClientConnectionManager 를 하나 선언해 두고, 최초로 getHttpClient를 호출할 때 Connection Pool이 지정된 사이즈로 생성된다. 그리고, Connection을 하나 만들어 리턴한다.

- maxTotal : Connection Pool의 수용 가능한 최대 사이즈
- defaultMaxPerRoute : 각 host(IP와 Port의 조합)당 Connection Pool에 생성가능한 Connection 수


protected <T> T get(String url) {
HttpClient httpClient = null;
HttpGet httpGet = null;
HttpResponse response = null;
try {
httpClient = ConnectionManager.getHttpClient();
httpGet = new HttpGet(url);
response = httpClient.execute(httpGet);

StatusLine statusLine = response.getStatusLine();
// 에러 발생
if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) {
throw new Exception(statusLine.getStatusCode(), getReason(response));
}
// 성공
else {
                            // do something
}
} catch (ClientProtocolException e) {
e.printStackTrace();
ConnectionManager.abort(httpGet);
throw e;
} catch (IOException e) {
e.printStackTrace();
ConnectionManager.abort(httpGet);
throw e;
} finally {
ConnectionManager.release(response);
}
}

Connection Pool은 요런 식으로 이용하면 되겠다. 위는 GET 방식 사용시.


Pool을 사용할 때마다 항상 주의할 것은 역시 반환을 꼭 해 줘야 한다는 것!!!
Exception이 발생하면 abort를 호출해서 정리해 주고, finally에선 항상 EntityUtils.consumeQuietly를 호출해서 리소스를 반납하게 해 준다.


AND

IE나 Firefox에서는 보통 쓰던대로 아래와 같이 넘기고 받으면 끝이었다.


Parent.html

    var retVal = window.showModalDialog("popup.html", null,);

    if (retVal) {

        $("#some_id").val(retVal.some_id);

        $("#some_name").val(retVal.some_name);

    }


popup.html

function fn_Click(some_id, some_name) {

    var obj = new Object();

    obj. some_id  =  some_id ;

    obj. some_name = some_name ;

    window.returnValue = obj;

    self.close();

}


그런데, Chrome에서는 popup에서 받은 retVal이라는 변수가 undefined이다.

이럴 때 아래와 같이 Chrome 브라우저에 대한 추가처리코드를 추가해 주면 해결된다.


Parent.html

    var retVal = window.showModalDialog("popup.html", null,);


    if (retVal == undefined)

    retVal = window.returnValue;


    if (retVal) {

        $("#some_id").val(retVal.some_id);

        $("#some_name").val(retVal.some_name);

    }


popup.html

function fn_Click(some_id, some_name) {

    var obj = new Object();

    obj. some_id = some_id ;

    obj. some_name = some_name ;

    if (window.opener)

        window.opener.returnValue = obj; 

    window.returnValue = obj;

    self.close();

}



'Java_Web' 카테고리의 다른 글

대용량 파일 조회 및 문자열 찾기  (0) 2015.03.10
HttpClient Connection Pooling  (0) 2015.03.10
JD Java Decompiler  (0) 2010.04.19
Hibernate에서 CLOB 컬럼 사용하기  (0) 2009.12.16
HQL UNION 제약사항  (0) 2009.08.21
AND

JD Java Decompiler

Java_Web 2010. 4. 19. 14:43
한동안 잘 쓰던 Java Decompiler인 DJ(맞나?)가 유료화되면서 무료버전을 찾아 쓰기가 귀찮았는데,
막강한 프로그램을 찾았다.
이미 아는 분들이 많을 것 같기도 한데, 나는 며칠 전에야 보았다. ^^;
class 파일 뿐 아니라, JAR 파일의 연결프로그램으로 등록해서 사용하면 더 좋다.

아래와 같이 JAR 파일을 열면 구조가 보이면서,
그중 class파일을 선택하면 바로 오른쪽에 디컴파일된 자바소스가 보인다.
왼쪽엔 클래스의 구조도 풀어서 보여 준다.
아직 더 상세한 기능은 못 봤지만, 이것만으로도 아주 강력하다.


아래는 다운로드 받을 수 있는 주소(프랑스에서 만든 것 같고, 무료다)
설치과정도 없고, 그냥 압축파일 풀어서 나온 실행파일만 실행하면 된다.

http://java.decompiler.free.fr/

들어가 보면, JD-Core가 decompiler 엔진인 것 같고,
위의 스크린샷은 JD-GUI 이다.
Eclipse에서 Source Attach가 안 된 class 파일도 바로 decompile해서 볼 수 있는 것이 JD-Eclipse이다.
어쩌면 이렇게 딱 입맛에 맞게 만들었는지...
저절로 기부하고 싶게 만드는 프로그램이다.

AND

Hibernate에서 오라클 CLOB 컬럼을 사용하려면,

매핑파일에서 컬럼의 type을 text로 설정하고,
[code xml] <property name="clob"  type="text" column="CLOBCOLUMN" /> [/code]

이 컬럼에 해당하는 Value Object의 변수는 String으로 선언해서 사용하면 된다.
[code java] private String clob; [/code]

혹시 이렇게 했는데 에러가 나면 최신 오라클 JDBC 드라이버로 바꿔 보는 정도의 센스는 기본~

AND

HQL UNION 제약사항

Java_Web 2009. 8. 21. 17:27
요즘 Hibernate가 필요해서 이것저것 해 보고 있다.
특히 장점은 한 번 프로그램을 작성해 놓으면 설정만으로 다른 종류의 DBMS에서도 그대로 사용이 가능하다는 거다. 그래서, 열심히 기존에 있던 오라클용 SQL을 HQL로 변경해서 테스트해 보는데 이거 제약이 좀 있다.
참고로, HQL 테스트환경은 Hibernate Tools를 Eclipse에 설치해서 Configuration 잡아 주고, Hibernate perspective에서 Hibernate Configurations 탭을 통해 HQL Editor를 띄우면 일반 DB client처럼 쿼리 결과도 볼 수 있고, HQL로부터 실제로 생성되는 SQL도 볼 수 있다.

일단, 인터넷에서 HQL에서 UNION이 지원되나 찾아 보면 안 된다는 내용이 대세다.
하지만, 내가 오라클을 써서 그런지 내가 쓰는 Hibernate 버전에서 지원되는 건지 모르겠지만 UNION만은 잘 된다. 3개를 묶어도 잘 된다. 그런데, UNION을 IN의 subquery에서 사용하면 못 한다고 에러가 난다.

[code sql] from t1 where col1 in (    select col2 as name1    from t2    where col3 = 'a'    union    select col2 as name1    from t3    where col3 = 'a' ) [/code]
처음엔 에러 메시지가 union 부분이라고 해서 union을 못 쓰는 줄 알았는데, 하다보니 in 안의 subquery에서 사용해서 그런 거였다. 결국 그래서 아래와 같이 바꿨다.

[code sql] from t1 where col1 in (   select col2 as name1   from t2
   where col3 = 'a' ) union from t1 where col1 in (   select col2 as name1   from t3   where col3 = 'a' ) [/code]
덕분에 쿼리가 왕창 길어졌다. ㅡ.ㅡ;
이렇게 쿼리 만드는 일이 거의 없나? 하긴 나는 원래 그냥 UNION도 거의 안 써 봤으니...
암튼 잘 되기만 하면 된다.

'Java_Web' 카테고리의 다른 글

JD Java Decompiler  (0) 2010.04.19
Hibernate에서 CLOB 컬럼 사용하기  (0) 2009.12.16
다국어 코드 적용하기  (0) 2009.03.02
오라클에서 SYSDATE를 이용한 시간 연산  (0) 2009.02.12
Derby 사용하기  (2) 2008.09.29
AND


대단한 프로그램은 아니고,
이번에 프로그램을 한참 개발하고 다국어 버전으로 변경할 일이 있었는데
30여개의 소스 파일을 일일이 뒤지면서 한글로 만들어 놓은 코드를 다국어 처리코드로 바꾸는 게 여간 힘든 작업이 아니었다.
(눈과 팔이 너무 아팠다. ^^;)
만만하게 보고 그냥 무식하게 하려다가, 결국 반 정도를 한 후에 프로그램을 만들었다. ^^;

아래에 있는 소스가 자바로 만든 변환 프로그램.

[code java]
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;

public class I18NReplace {
    private static final String I18N_CODE_PREFIX = "{resourceManager.getString('rb','";
    private static final String I18N_CODE_POSTFIX = "')}";

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        BufferedReader br = null;
        BufferedReader brSrc = null;

        try {
            // 다국어 코드, 값이 정의된 리소스번들 파일
            br = new BufferedReader(new InputStreamReader(
                                    new FileInputStream("code.properties"), "UTF-8"));
            // 다국어 버전으로 변환할 소스 파일
            brSrc = new BufferedReader(new InputStreamReader(
                                    new FileInputStream("test.mxml"), "UTF-8"));
           
            String line = null;
            // 다국어 코드, 값을 HashMap에 저장
            while ( (line = br.readLine()) != null ) {
                String[] arr = line.split("=");
                if (arr.length == 2) {
                    map.put(arr[0], arr[1]);
                }
            }
           
            Object[] keys  = map.keySet().toArray();
            // 길이가 긴 문자열부터 변환하기 위해 문자열 길이 내림차순으로 키를 정렬
            Arrays.sort(keys, new KeyOrderComparator());
           
            String srcLine = null;
            while ( (srcLine = brSrc.readLine()) != null ) {
                // 주석 라인은 skip
                if (!srcLine.trim().startsWith("//") &&
                    !srcLine.trim().startsWith("/*") &&
                    !srcLine.trim().startsWith("<!--")) {
                    for (int i = 0; i < keys.length; i++) {
                        if (srcLine.indexOf((String)keys[i]) > -1) {
                            srcLine = srcLine.replaceAll((String)keys[i],
                                    I18N_CODE_PREFIX + (String)map.get(keys[i]) + I18N_CODE_POSTFIX);
                        }
                    }
                }
               
                System.out.println(srcLine);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null)
                    br.close();
            } catch (Exception e) {}
           
            try {
                if (brSrc != null)
                    brSrc.close();
            } catch (Exception e) {}
        }
    }
}

class KeyOrderComparator implements Comparator {
    /**
     * 문자열의 길이에 따라 내림차순 정렬
     */
    public int compare(Object key1, Object key2) {
        return ((String)key2).length() - ((String)key1).length();
    }
}
[/code]
I18NReplace.java

[code]
데이터=data
[/code]
code.properties

[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" title="데이터">
...
</mx:TitleWindow>
[/code]
test.mxml


사용법이 복잡하진 않지만, 급하게 대강 만든 프로그램이라 GUI 등의 친절한 기능은 없다.

1. code.properties는 코드와 값을 넣은 건데 일반적인 순서와는 반대다.
이유는, 다국어 적용하기 전에 '데이터'라고 코딩을 했는데 이를 다국어 처리하기 위해서는
한글용 리소스번들 파일에 'data=데이터' 라는 내용이 있어야 한다.
그리고, '데이터' 대신에 다국어 처리 코드( Flex를 예로 들면, {resourceManager.getString('rb','data')} )를 넣으면
언어 설정에 따라 한국어일 때는 '데이터', 영어일 때는 'Data'라는 내용이 보이게 된다.
그런데, 지금 할 일이 '데이터'라고 하드코딩되어 있는 부분을 {resourceManager.getString('rb','data')}로 바꾸는 것이니까
code.properties 파일에는 '데이터=data' 즉, message=key 형태로 되어 있어야 한다.

2. test.mxml은 '데이터'라는 내용이 하드코딩되어 있는 소스 파일

3. I18N_CODE_PREFIX는 다국어처리코드에서 key 앞에 붙는 코드, I18N_CODE_POSTFIX는 key 뒤에 붙는 코드

참고로, 중간에 Arrays.sort 는 문자열길이에 따라 내림차순으로 정렬하는 건데,
그래야 '데이터 관리'라는 문자열이 소스에서 나왔을 때, '데이터 관리'라는 게 code.properties에 있을 때 우선적으로 처리하고
이게 없으면 '데이터'나 '관리'를 따로 변환하게 되어 원치 않는 결과가 나오게 된다.
이렇게 '데이터 관리'를 하나의 단어로 정의하는 것은 언어에 따라 어순이 바뀌거나,
두 단어 이상이 합쳐져서 만들어진 단어의 경우 언어에 따라 다른 의미가 될 수 있어서이다.

또, 참고로 파일을 읽을 때 "UTF-8"라는 설정이 있는데 이것은 "UTF-8" 인코딩으로 저장한 파일을 읽기 위해 사용한 것이다.
소스 파일이나 리소스번들 파일의 인코딩 형태에 따라 빼거나 고쳐서 사용하면 된다.


이렇게 설정한 상태에서 이 I18NReplace을 실행하면 치환된 결과가 표준출력으로 나오고,
이 결과를 원래 소스에 붙인 다음에 잘못 된 부분을 수정해서 저장하면 다국어 처리가 끝난다.

꼭 다국어 처리 변환에만 사용할 필요는 없고, 어떤 규칙에 따라 문자열을 치환해야 하는 경우 사용하면 되겠다.
당연한 얘기지만, code.properties 파일 정의시 띄어쓰기는 정확히 맞춰 주어야 한다.
AND

[code sql] SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD HH:MI SS') "NOW", TO_CHAR(SYSDATE+1, 'YYYY/MM/DD HH:MI SS') "1 DAY AFTER", TO_CHAR(SYSDATE+1/24, 'YYYY/MM/DD HH:MI SS') "1 HOUR AFTER", TO_CHAR(SYSDATE+1/24/60, 'YYYY/MM/DD HH:MI SS') "1 MINUTE AFTER", TO_CHAR(SYSDATE+1/24/60/60, 'YYYY/MM/DD HH:MI SS') "1 SECOND AFTER" FROM DUAL [/code]
당연히,
SYSDATE에 숫자를 더하면 현재시간 이후를 의미하고,
SYSDATE에서 숫자를 빼면 현재시간 이전을 의미

이런 쿼리를 사용하면 오라클이 아닌 다른 DB를 사용하게 되면
프로그램 고치는 일이 엄청나게 커지니까 오라클만 쓸 생각일 경우에만 사용해야 할 듯
시스템 운영자와 사이가 안 좋을 땐 이런 지뢰를 심어 놓는 방법이... ㅋㅋ

'Java_Web' 카테고리의 다른 글

HQL UNION 제약사항  (0) 2009.08.21
다국어 코드 적용하기  (0) 2009.03.02
Derby 사용하기  (2) 2008.09.29
자바에서 바이트 단위길이로 문자열 자르기  (0) 2008.07.01
ASM - 자바 바이트코드 분석하기  (1) 2008.03.07
AND

Derby 사용하기

Java_Web 2008. 9. 29. 18:03

가볍고 빠른 DB를 쓸 일이 있었는데 마침 딱 맞을 것 같아서 고른 Derby.

올해 초에 간단하게 써 보긴 했는데, 제대로 알고 쓰려고 하니 공부할 것이 꽤 많았다. 그래서, 열심히 삽질하면서 공부해서 필요한 걸 거의 3주만에 만들었는데, 그동안 공부한 것을 혹시 그냥 잊어 버리지 않을까 하는 마음에 정리도 할겸, 혹시 필요한 분들이 있으면 도움이 될까 해서 이렇게 글을 남겨 본다.

단, 여기서는 Embedded는 배제하고, Network Server를 중심으로 설명한다.


1. Derby

Derby는 관계형 데이터베이스의 하나로 맨 처음 IBMCloudscape라는 이름으로 WebSphere 등에서 사용하다가 Apache에 오픈소스로 공개되면서 Derby라는 이름을 갖게 되었다. 그후 Sun에서 JavaDB라는 이름을 붙여서 배포하게 되었고, JDK 6에 기본으로 포함되어 있다.

무료로 가볍고 간단하게 구성할 수 있으며 안정성이 높다. 다른 오픈소스 DB들에 비해 성능이 좀 떨어지지만 안정성이 우수하.


2. Derby의 연결방식

- Embedded 방식 : 하나의 JVM 내에 어플리케이션과 DB가 함께 존재하도록 구성하는 방식으로 다른 JVM이나 머신에서는 이 DB에 접근이 불가능하다.

- Network Server 방식 : 어플리케이션과 DB가 다른 JVM 또는 프로세스에 존재하도록 구성하는 방식으로 각 인스턴스가 하나의 JVM 내에서 동작하고, 일반적인 JDBC와 똑같은 방식으로 접근이 가능하다.

FISS의 경우, FISS_RULE을 비롯해 많은 커넥터들이 각기 다른 JVM으로 떠서 동작하고, 여기서 접근이 가능해야 하므로 Network Server 방식을 사용한다.

그러므로, 이하는 Network Server 방식을 기반으로 설명한다.

참고로, embedded 방식과 Network Server 방식은 JVM 외부에서의 접근가능여부와 Connection URL에서 IP가 들어가는 것 빼곤 사용방법이 동일하다.


3. Derby 인스턴스 기동

org.apache.derby.drda.NetworkServerControl 클래스를 실행시켜 기동(start), 정지(shutdown), (ping) 등의 작업을 수행할 수 있다.

java $JAVA_OPTION -classpath $CLASSPATH org.apache.derby.drda.NetworkServerControl start -p 54321 -h 192.168.1.123

-p 옵션은 인스턴스가 사용할 포트를 의미하며, 사용하지 않으면 Derbydefault 포트인 1527을 사용한다.

-h 옵션은 인스턴스가 요청을 받을 호스트를 나타내며, 호스트를 설정하면 그 외의 호스트명으로는 접근이 불가능하다. 제한을 두지 않으려면 이 옵션을 사용하지 않는다.


4. DB 연결 및 생성

사용자 계정을 이용한 연결기능도 지원하지만 여기서는 사용하지 않으므로 설명을 생략하고, 계정정보 없이 접속하면 defaultAPP라는 스키마를 사용한다. (계정정보를 이용해 접속하면 그 사용자 ID에 해당하는 스키마를 사용하게 된다.)

Derby는 특별히 DB를 생성하는 명령이 있지 않고, Connection URL의 끝에 “create=true”를 붙여 주었을 때 이 Connection URL에 해당하는 DB가 있으면 그냥 연결만 하고, 해당 DB가 존재하지 않으면 새롭게 생성한다. Connection URL에 “create=true”가 없을 때 해당 DB가 없으면 연결 오류가 발생한다.

DerbyJDBC 방식으로 연결하기 위한 Connection URL은 다음과 같다.


jdbc:derby://{IP}:{Port}/{DB생성디렉토리}/{DB};create=true

- IP : Derby Network Server가 떠 있는 머신의 IP를 나타내는데, 기동시 -h 옵션을 사용했다면 여기에 사용한 호스트명과 똑같이 입력해야 연결이 가능하다.

- Port : Derby Network Server기동할 때 -p 옵션을 사용했다면 여기서 사용한 포트번호를 입력한다. -p 옵션을 사용하지 않았다면 default 포트인 1527을 사용한다.

- DB생성디렉토리 : DB파일이 생성되는 디렉토리를 지정하고 싶으면 여기에 설정한다. , 디렉토리는 루트부터 시작하는 절대경로를 입력해야 하고, 그렇지 않으면 JVM을 실행하는 디렉토리의 하위디렉토리를 생성하고 거기에 DB파일을 생성한다. 루트부터 입력하므로 유닉스는 슬래쉬(/)2번 반복된다. 생략하면 JVM이 실행되는 디렉토리가 default이다.

- DB: DB명도 되고, DB데이터파일이 저장되는 디렉토리명도 된다. DB생성디렉토리를 설정했으면 그 하위에 디렉토리로 생기고, 그렇지 않으면 JVM이 실행된 디렉토리 아래에 생성된다.

- create=true : DB생성디렉토리에 해당 DB명이 있으면 상관없는데, 해당 DB명이 없으면 그 위치에 DB데이터 디렉토리를 자동으로 생성한다. 이 옵션을 사용하지 않으면 해당디렉토리에 해당 DB가 있으면 연결하고, 없으면 오류가 발생한다.


윈도우 예> jdbc:derby://localhost:1527/C:/derby/RULEDB1;create=true

UNIX > jdbc:derby://localhost:1527//usr/derby/RULEDB1;create=true


JDBC 방식의 연결은 DerbyJDBC Client 드라이버인 org.apache.derby.jdbc.ClientDriver를 로딩하고 위의 URL을 이용해서 DriverManager.getConnection을 호출하면 된다.


예제 코드>

Class.forName(org.apache.derby.jdbc.ClientDriver).newInstance();

DriverManager.getConnection(“jdbc:derby://localhost:1527/C:/derby/RULEDB1;create=true”);


그 밖에도 Derby에서 제공하는 DataSource도 이용 가능하다.


참고사항 : 하나의 인스턴스를 통해서 여러 DB에 접근할 수 있다. 하지만, 여러 인스턴스가 동시에 하나의 DB를 사용할 수는 없다. 그러므로, 어떤 DB를 어떤 인스턴스가 사용할지 명확하게 해 두어야 한다.

1527포트의 인스턴스를 통해 ruledb1ruledb2를 접근할 수는 있지만, 1527포트의 인스턴스와 1528포트의 인스턴스가 동시에 ruledb1이라는 DB를 접근할 수는 없다는 뜻이다.

※ Derby에 connection을 만들자 마자 autocommit을 false로 설정하는 것이 좋다. 그렇지 않으면, CUD 뿐만 아니라 R 처리에서도 부하가 가해지면 오류가 잔뜩 떨어지면서 문제가 발생한다. 하지만, autocommit을 false로 해 놓으면 꽤 많은 부하가 가해져도 오류없이 잘 처리해낸다. 단, 이렇게 되면 CUD는 무조건 commit, rollback을 해 줘야 한다.


5. 쿼리 수행

일반 JDBC동일하.


참고사항 : Derby에서 가장 빨리 수행될 수 있는 쿼리는 ‘SELECT 1 FROM SYSIBM.SYSDUMMY1’ 이다. SYSIBM.SYSDUMMY1 테이블은 오라클의 DUAL과 비슷한 기능이다.


- 오늘날짜 : SELECT CURRENT_DATE FROM SYSIBM.SYSDUMMY1 → 2008-09-29

- 현재시간 : SELECT CURRENT_TIME FROM SYSIBM.SYSDUMMY1 → 17:42:06

- 타임스탬프 : SELECT CURRENT_TIMESTAMP FROM SYSIBM.SYSDUMMY1 → 2008-09-29 17:42:13.328

시간이나 날짜를 문자열로 포맷팅하는 함수는 제공하지 않는 것 같다. 아마도 위에서 나오는 문자열을 substring으로 잘라서 쓰거나, 원래의 데이터타입을 이용해 setDategetDate를 이용해야 할 것 같다.


6. ij (Derby용 유틸리티 프로그램) 사용

DerbyNetwork Server로 기동시키면 EclipseData Source Explorer로 접속도 가능하고, Derby와 함께 제공되는 ij라는 유틸리티로도 접근이 가능하다.

- ij 실행

명령프롬프트에서 $DERBY_HOME/binij 를 실행한다.

C:\derby\bin> ij

- DB 연결

connect 명령과 connection URL을 이용해서 DB에 연결한다.

ij> connect 'jdbc:derby://localhost:1527/C:/derby/RULEDB1';

- ij 명령어 보기

ij> help;

- 스키마 목록 보기

ij> show schemas;

- 현재 연결된 DB들에 대한 연결정보 보기

ij> show connections;

- 테이블 리스트 보기

ij> show tables;

- 테이블 구조 보기

ij> describe {테이블명};

- ij 끝내기

ij> exit;

- 그 밖에 모든 DDL, DML 수행 가능하며, 끝은 반드시 세미콜론(;)으로 끝나고 엔터를 쳐야 실행된다.




Derby를 이용해서 프로그램을 다 만든 다음에 용도에 딱 맞는 프로그램을 하나 찾았다.

Memcached(멤캐쉬디)라고 아마도 Memory Cache Daemon의 줄임말이 아닐까 생각이 된다.

웹페이지 로딩을 빠르게 하기 위해 만들어졌다고 본 것 같은데,

키와 Object의 pair로 저장이 되고, 정해진 메모리양을 넘어가면 LRU 방식으로 비워 가면서 정보를 추가한다.

데몬을 띄울 때 메모리의 양을 할당하는 옵션이 있고,

2개 이상의 동일한 내용을 담을 데몬을 띄워 놓을 수 있는데

클라이언트 객체를 만들 때 이들 데몬의 호스트명과 포트명을 한꺼번에 주면

데몬 하나가 죽을 때 다른 살아있는 데몬에 읽고 쓸 수 있기 때문에 장애에 대한 대처도 가능하다.

단, 소스가 C로 작성되어 있어서 실행하려면 OS에 따라 컴파일해서 사용해야 한다.

유명한 OS(윈도우 포함)인 경우는 컴파일해서 올려 놓은 프로그램을 다운받아서 사용해도 된다.

클라이언트는 다양한 언어로 구현된 것이 올라와 있기 때문에 골라서 사용하면 된다.

나는 윈도우용으로 컴파일된 프로그램을 설치하고 띄운 후에,

자바 클라이언트로 접속해서 사용해 봤는데 잘 작동했고 성능도 괜찮았던 것 같았다.

(몇 달 만에 기억을 더듬으며 쓰려니 가물가물하넹.)

참, 이것도 오픈소스이다.

참고로 테스트용으로 사용한 자바 소스파일 첨부했으니 참고하시길...

여러 데몬에 한꺼번에 연결하려면 아래와 같이 고치면 됨.

MemcachedClient c = new MemcachedClient(AddrUtil.getAddresses("server1:11211 server2:11211"));



자세한 내용은 아래의 홈페이지 참조 요망~

www.danga.com/memcached/


자바 클라이언트 download 및 설명

http://code.google.com/p/spymemcached/




AND