336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
현재 제가 일하고 있는 환경에서는 로드밸런싱(Load Balancing)을 하기 위해 L4 스위치(이하 L4)를 이용합니다. 많은 분들이 잘 알고 계시겠지만 L4는 전방에 위치하면서 특정 서버군으로 들어오는 요청을 서버군 내의 각 장비로 균형있게 전달해주는 역활을 합니다.

그런데 L4를 이용하여 로드밸런싱을 하려면 최소한 물리적 장비가 2대 이상 있어야 합니다. 따라서 요청이 매우 적어 장비 1대만으로도 충분히 서비스가 가능할지라도, 로드밸런싱을 위해서 최소 2대를 배치해야 합니다.

소프트웨어 로드밸런싱이 가지는 의미는 위와 같은 점에서 찾을 수 있다고 생각합니다. 아파치-JK-톰캣으로 연결되는 소프트웨어 로드밸런싱은 1대의 장비만으로도 서버가 감당할 수 있는 만큼의 로드밸런싱을 가능하게 해줍니다. 주목해야 할 점은 최소 2대의 장비가 필요했던 것을 1대의 장비로 해결할 수 있다는 점입니다. 거기에 스위칭 비용도 약간의 CPU와 메모리만 사용하면 되니 거의 공짜라고 할 수 있겠습니다.

그렇다면 성능은요? 예 아쉽게도 성능은 하드웨어로 구동되는 L4 로드밸런싱에 비해 떨어질 수 밖에 없습니다. 하지만 때로는 성능이 큰 관심사가 아닐 때도 있습니다. 바로 요청이 거의 없는많지 않은 서비스의 경우입니다. 이런 서비스들에 로드밸런싱을 도입하려는 이유는 성능보다는 안정성 때문일 것입니다. 즉 하나가 서비스 불능 상태에 빠지더라도 나머지 하나가 서비스를 정상적으로 해주길 바라는 경우인 것이죠.

이번에 저희 팀의 상황도 위와 같이 성능보다는 안정성이 필요한 상황이였습니다. 1대의 장비에 2개의 서비스를 설치했습니다. 그리고 각 서비스들의 요청은 아파치와 JK를 통해 2개의 톰캣으로 로드밸런싱 되게 만들었습니다. 총 4개의 톰캣 프로세스가 동작하게 된 것입니다.  만약 기존대로 L$를 이용했었더라면 얼마나 더 많은 비용이 들었을까요?

사실 이번의 소프트웨어 로드밸런싱은 운영팀에 제안으로 이루어진 일입니다. 저는 이것이 정말 현명한 제안이였다고 생각합니다. 게다가 제안을 실행에 옮기고 나니 제 마음도 많이 기쁘네요. :)


WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
웹개발을 하면서 한글은 매우 어려운 문제입니다. 제 경우에는 한글 문제만큼은 단기 학습을 통해서 해결이 되지 않았습니다. 온갖 문서를 읽어보고 실험해보고 했지만 정확히 이해하는 것은 매우 힘들었습니다. 또한 조금 이해한 것 같으면 계속해서 새로운 문제가 등장하였습니다. 지금도 여전히 깊은 이해는 없다고 생각합니다. 하지만 경험을 정리하면 누군가에게는 도움이 될수도 있으리란 기대에 내용을 정리해봅니다.

1. 서버 측면에서 POST와 GET의 처리의 차이점

저는 POST로 보내든 GET으로 보내든 request.setCharacterEncoding(CHARSET)만 호출해주면 자바 내에서의 한글처리는 완료된다고 알고 있었습니다. 하지만 실제로는 그렇지 않았습니다. request.setCharacterEncoding(CHARSET)는 POST방식에만 유효합니다. GET방식의 캐릭터셋 정의는 톰캣의 경우 server.xml의 Connector 설정쪽에 URIEncoding라는 옵션을 통해서 가능합니다. 이 옵션에 캐릭터셋을 정의하게 되면 GET방식으로 오는 파라메터에 대해서 해당 캐릭터셋을 이용하여 디코딩을 합니다.(만약 지정되어 있지 않다면 ISO-8859-1을 기본적으로 사용하게 됩니다.)

결론적으로 POST와 GET을 모두 정확하게 처리하려면 setCharacterEncoding뿐만 아니라 URIEncoding이 정의가 되어 있어야 합니다. 또한 GET방식은 브라우저에 따라서 자동으로 URL인코딩을 해주지 않는 경우가 있기 때문에 GET파라메터에 대해서는 항상 URL인코딩을 해주는 것이 좋을 것으로 보입니다.

2. 항상 setCharacterEncoding을 해주어야 하는 이유?

어느 순간 왜 항상 setCharacterEncoding을 해줘야 하는지에 대한 의문이 들었습니다. 송신측에서 내가 보내는 데이터가 어떤 타입인지 명시해주고 수신측에서 그 값에 따라 동적으로 처리해주면 좋을텐데라는 생각이 들었습니다. 처음에는 HTTP 해더를 살펴보면 뭔가 답이 있을 것이라 생각했습니다. 파이어폭스의 플러그인인 파이어버그를 이용하여 서버로 날아가는 HTTP 패킷을 살펴보았습니다. 비슷해 보이는 해더를 찾아볼 수가 없었습니다. HTTP RFC를 열심히 뒤져보았습니다. 그러나 요청에서 사용할 수 있는 해더 후보 목록 중에서는도무지 해당 내용을 찾아볼 수가 없었습니다. HTTP 1.1이 다국어에 대한 고려를 미처 하지 못해 지원이 안 되는구나라고 생각했습니다.

3. Connector의 useBodyEncodingForURI 옵션을 발견

그러던 중 톰캣의 useBodyEncodingForURI 라는 옵션의 명세를 보게됐습니다. 내용은 아래와 같았습니다.


This specifies if the encoding specified in contentType should be used for URI query parameters, instead of using the URIEncoding. This setting is present for compatibility with Tomcat 4.1.x, where the encoding specified in the contentType, or explicitely set using Request.setCharacterEncoding method was also used for the parameters from the URL. The default value is false.

그 중 위 밑줄 처친 부분이 심상치 않게 느껴졌습니다. 분명히 "contentType안에 명시된 인코딩"이라고 하고 있습니다. contentType이라는 해더가 분명히 있을 것이라 생각하게 되었습니다. 

4. Content-type=application/x-form-urlencoded;charset=UTF-8 발견

문제에 대해 여러 지인들에게 질문을 던지던 중 제 사수이시기도 했던 선배분이 톰캣 소스를 보고 HTTP 해더에 "Content-type=application/x-form-urlencoded;charset=UTF-8"과 같이 보내주면 setCharacterEncoding을 하지 않아도 UTF-8로 파라메터가 읽혀진다는 것을 알려주었습니다. 정말 테스트해보니 아무것도 정의하지 않고 getCharacterEncoding을 하니 UTF-8이 반환되었습니다. 

5. 그렇다면 어떤 근거로?

처음 가졌던 의문에 대한 답은 된 셈이였습니다. 하지만 찝찝합니다. 어떤 근거로 톰캣이 이렇게 구현을 했는지가 궁금했던 것이였습니다. 이것 때문에 (나름대로는)많은 시간을 들여서 찾아본 결과 해당 내용에 대해 다룬 내용을 찾을 수 있었습니다.

HTTP/1.1 uses many of the constructs defined for Internet Mail (RFC 822 [9]) and the Multipurpose Internet Mail Extensions (MIME [7]) to allow entities to be transmitted in an open variety of representations and with extensible mechanisms.
http://tools.ietf.org/html/rfc2045#section-5

The purpose of the Content-Type field is to describe the data contained in the body fully enough that the receiving user agent can pick an appropriate agent or mechanism to present the data to the user, or otherwise deal with the data in an appropriate manner. The value in this field is called a media type. 
즉 HTTP에서 데이터(Entities) 전송을 위해 MIME 사용이 가능합니다. 그리고 그 중 Content-Type은 수신측이 우리가 보내는 데이터를 정확히 해석하는 것을 도와주기 위해 사용됩니다. 즉 Content-Type은 위의 "Content-type=application/x-form-urlencoded;charset=UTF-8" 와 같이 여러 가지 정보를 기술하기 위해 사용 될 수 있는 것입니다.

6. 남은 부분

하지만 아직까지 남아있는 궁금증이 있습니다. 그것은 바로 "브라우저에서 해더에 Content-Type을 함께 보내게 하려면 어떻게 해야하나"라는 문제입니다. 이 방법을 알게되면 서버에서 획일적으로 하는 setCharacterEncoding을 완전히 제거할 수 있습니다. 대신 톰캣은 해더에서 정보를 읽어 상황에 따라 적절하게 요청을 읽을 수 있을 것입니다. 이것은 블로그의 트랙백과 같은 기능에서 유용하게 이용될 수 있을 것 같습니다. 트랙백은 여러 서비스들에서 보내며 그들의 인코딩은 다양할 수 있기 때문입니다. 

이 부분을 찾아내기 위해여 여러 테스트를 해보았으나 아직 찾지 못했습니다. 그래서 우선은 알아낸 부분까지 기록하고 나중에 이 문제와 관련한 또다른 지식이나 이해가 생기게 되면 계속해서 이 포스트에 정리해나가도록 하겠습니다. 

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Webwork에는 재밌는 기능이 하나 있습니다. 그것은 요청 파라메터를 모델 객체로 바로 매핑 시켜주는 기능입니다. 예제 코드를 만들어 보았습니다.

public class Param extends ActionSupport {

    private final static Log log = LogFactory.getLog(Param.class);
    private Person person;
   
    public String execute() {
        log.info(person.getName());
        log.info(person.getAge());
        return SUCCESS;
    }
    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    private class Person {
        private String name;
        private Integer age;

        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}

위와 같은 액션을 만든 후 아래와 같이 접근해보면,
http://localhost:8080/Param.action?person.name=Min&person.age=90

콘솔에 이름과, 나이가 찍히게 됩니다. GET파라메터가 객체로 바로 매핑 된 것입니다. 내부적으론 Person객체의 setter들이 호출 될 것입니다.

처음 보았을 때는 상당히 유용한 기능이라고 생각했습니다. 반복되는 코드의 작업을 줄여줄 것이라 생각했기 때문입니다. 하지만 좀 더 사용하다보니 절대 사용하지 말아야 하는 기능 중에 하나라고 생각하게 되었습니다.

이유는 "모델 객체가 요청 파라메터의 영역으로까지 영향을 미치게 되었기 때문"입니다. 즉 모델과 요청 파라메터 사이에는 강한 결합(Tight coupling)이 발생한 것입니다. 만약 위의 person.name, person.age와 같은 파라메터가 여기 저기(JSP or JS 등)에서 사용되고 있는 상황에서, 어떤 이유로 Person 객체의 name 필드가 nickname으로 수정되고 setter 또한 setNickname으로 수정되어야 한다고 가정 해봅시다. 이 때 여기 저기 노출되어 있는 파라메터명이 모두 수정(person.name->person.nickname)되어야 합니다. 아마 단순히 서블릿만을 사용해 본 신입 개발자도 의아한 표정으로 물어볼 것입니다.

"모델 클래스를 변경했을 뿐인데 왜 파라메터명들이 모두 바뀌어야하죠? 제 서블릿 코드는 자바 코드만 고치면 되는데요."

그렇습니다. 지금 상황에선 차라리 예전 서블릿에서 많이 쓰던 아래와 같은 코드가 낫습니다.

String name = request.getParameter("name");
String age = request.getParameter("age");
Person person = new Person();
person.setName(name);
person.setAge(age);

(참고. 서블릿과 구 프로그래밍 방식이 무조건 좋다는 말은 아닙니다. 요청과 모델 사이에 매핑 코드가 존재하는 구조가 좋다는 말입니다.)

위와 같은 코드는 모델이 바뀐다고 요청 파라메터까지 바뀌지 않아도 됩니다. 우리는 이것을 흔히 "느슨한 결합( Loose coupling)"이라고 얘기합니다. 사실 원래 신경쓰지 않아도 우리가 늘 해온대로만 코딩해도 느슨하게 이루어지던 부분입니다. 그런데 위의 경우에는 소위 새로운 무엇이라고 불리는 것이 오히려 기존의 장점을 없애고 해악을 끼쳤다고 볼 수 있겠습니다.

그렇기 때문에 우리는 새로운 기술을 적용할 때 좀더 신중해야합니다. 새로운 것이 항상 긍정적인 효과만을 가져다주진 않기 때문입니다. 그리고 위 서블릿의 예에서 볼 수 있듯이 오래된 방식이라고 해서 항상 퇴출해야 하는 것만은 아닙니다. 오래된 것은 오래된 것 나름대로의 숙성된 향이 있기 때문입니다. :)

예상 반론. 누군가는 변화가 발생했을 때 기존의 setter 인터페이스를 그대로 유지하면서 새로운 인터페이스로 위임하면 되지 않겠느냐고 말할 수 있겠습니다. 물론 이것은 분명히 하나의 해결책이 될 수 있습니다. 하지만 좋은 해결책은 아닙니다. setter가 많아짐으로 인해 클래스의 복잡도(setter가 많은 것도 복잡도가 높아지는 요인이라고 생각합니다)가 증가하기 때문입니다. 또한 구 setter들 위에 다른 개발자를 위해 주석을 달아놔야 할 것입니다. "기존 웹 파라메터와의 호환성을 위해 유지함"이라고 말입니다.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
예전에 JVM(Java Virtual Machine) 옵션 튜닝에 빠져 관련문서를 열심히 보던 때가 있었습니다. JVM 옵션을 튜닝하면 뭔가 그럴싸한 성과를 낼 수 있을 것이라 생각했었습니다. JVM 옵션 튜닝에 대해 뭔가 큰 환상이 있었습니다. 그러던 중, 꽤 큰 시스템을 대상으로 JVM 옵션을 튜닝해볼 수 있는 기회가 주어졌습니다. 기쁜 마음으로 다양한 실험을 해보았습니다. 하지만 결과는 만족스럽지 않았습니다. 가비지 콜렉션 시간을 약간 줄이고, 자바 프로세스가 시스템의 물리 메모리를 좀 더 천천히 가져가게 하였습니다. 안타깝게도 이게 전부였습니다. 가시적으로 눈에 확 띄는 효과가 없었습니다. JVM 옵션 튜닝에 대해 가졌던 환상이 깨졌고 많이 아쉬웠습니다. 하지만 남는 것이 있었습니다. 여러 옵션들의 사용법에 대해서 배울 수 있었던 것입니다.

당시 배웠던 옵션 중에는 JVM이 구동될 때 클라이언트 혹은 서버 모드를 선택할 수 있게 해주는 옵션이 있습니다. 실행 시 -client 옵션을 주면 클라이언트 모드로 구동되고, -server 옵션을 주면 서버 모드로 구동이 됩니다. 각 모드는 각각의 고유한 특징이 있습니다.
클라이언트 모드의 경우에는 빠른 시작과 적은 메모리 사용을 위해 최적화 되어 있습니다. 클라이언트 모드에서는 바이트 코드를 기계어로 컴파일 할 때 복잡한 최적화 기법을 이용하지 않습니다. 덕분에 코드를 분석하고 컴파일하는 시간이 서버 모드에 비해 훨씬 줄어들게 됩니다. 그래서 보다 빨리 시작되며, 컴파일 할 때 메모리도 적게 쓸 수 있습니다.
서버 모드의 경우에는 오랜 시간동안 실행되는 서버 애플리케이션에 최적화 되어 있습니다. 그렇기 때문에 C++ 컴파일러에서 쓰던 최적화 기법뿐만 아니라 더 진보된 많은 컴파일 최적화 기법들을 이용하여 컴파일을 합니다. 그래서 초기에는 컴파일 하는데 클라이언트 모드에 비교하여 좀 더 시간이 걸립니다. 대신 컴파일이 완전히 종료되면, 컴파일 된 코드에 실행에 대해서는 더 나은 속도를 보장하게 됩니다.

저는 이 옵션에 대해서 알게 된 이후로 제가 맡고 있는 모든 웹 애플리케이션 시스템을 서버 모드로 돌렸습니다. 일반적인 웹 애플리케이션은 구동된 후 같은 기능이 오랜 시간 동안 여러번 실행되기 때문에 주요 코드들은 반복적으로 자주 이용됩니다. 따라서 서버 모드가 적합하였습니다. 그 후로도 JVM이 구동되는 곳이라면 어디든지 서버 모드를 사용했습니다. 마치 버릇처럼 되어버렸습니다. GUI쪽의 애플리케이션을 만들지 않는 이상 클라이언트 모드를 사용할 만한 곳은 없다고 스스로 생각했습니다.

그 런데 어느날이였습니다. 로컬 개발 환경을 구성하다보니 새로운 생각이 떠올랐습니다. 그것은 로컬 개발 환경에서는 서버 모드 대신 클라이언트 모드를 사용하는 것이 더 좋을 것 같다라는 생각이였습니다. 클라이언트 모드가 더 좋을거라 생각한 근거는 톰캣이 일반적으로 짦은 시간 동안만 사용되며 자주 재시작이 되기 때문입니다. 저의 로컬 개발 환경을 예로 들어보겠습니다. 저는 톰캣을 씁니다. 기능에 대한 코드를 작성하고 톰캣을 띄웁니다. 그리고 알맞은 URL에 들어가 테스트를 해봅니다. 버그가 발견 됩니다. 버그를 고칩니다. 버그 수정본을 반영하기 위해 서버를 재시작(리로딩)합니다. 이런 과정을 하루에도 수십번 반복합니다. 이런 경우라면 주요 코드라 할지라도 몇번 호출되지 않습니다. 새로운 코드 반영을 위해 금방 톰캣이 꺼지기 때문이죠. 그래서 빠른 시작과 빠른 컴파일이 더 유용하다고 생각했습니다.

이 후 로컬 환경에는 클라이언트 모드를 사용하고 있습니다만, 사실 속도차를 체감하진 못합니다. 그래도 서버 모드보다는 나을 것입니다. 필요없이 복잡한 최적화를 하지 않을테니까요. :)


WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,