주요글: 도커 시작하기
반응형

최근에 특정 개발 방식을 시도할 겸, 몇 가지 구현 기술도 익힐 겸해서 회사에서 필요한 어플리케이션을 만들어보고 있다. 스프링 부트와 자바, 스칼라, JPA, AngularJS 등을 사용하고 있고, 뷰 템플릿으로는 JSP를 이용했다.


배포의 단순함을 위해 스프링 부트의 실행가능한 war를 만든 뒤, 로컬PC와 운영될 서버에 올려서 기능 테스트를 진행했다. 테스트를 진행하는 과정에서 다음과 같은 증상을 발견했다.

  • 리눅스 서버에서 war를 실행: 동일한 JSP 뷰를 실행할 때, 약 4초 간격으로 응답 속도가 느려짐. 예를 들어, 새로고침을 짧은 간격으로 실행하면, 30~40ms 걸리던 응답 시간이 약 4초마다 300~400ms가 걸림
  • 윈도우 개발 PC에서 war를 실행: 증상 없음
  • 맥 개발 PC에서 war를 실행: 리눅스 서버와 동일 증상 발생
  • JSP를 사용하지 않는 JSON 응답의 경우 비슷한 증상 없음
  • mvn spring-boot:run으로 실행할 때에는 맥 개발 PC에서 이런 증상이 발생하지 않음
한 JSP 코드에서 <jsp:include>를 이용해서 다른 JSP를 include 할 경우, 응답 속도가 더 느려졌다. 몇 가지 의심되는 것이 있어서 차례대로 확인해 나가다보니 결과적으로 응답 속도를 느리게 만드는 용의자를 찾았다. 그 용의자는 톰캣 JSP 엔진 Jasper의 JSP 변경 여부 검사 옵션이었다.

톰캣의 JSP 엔진인 Jasper는 기본적으로 다음과 같이 설정되어 있다.
  • 개발모드: JSP의 변경 여부를 확인할지 여부 지정. 기본 값은 true.
  • 변경확인간격: 개발모드가 true인 경우, 지정한 간격이 지날 때 마다 실행하는 JSP의 변경을 확인. 기본 값은 4초.
4초! 그렇다. 4초다. 앞서 이상 증상에서 발견된 그 약 4초라는 시간이 설정에 있는 것이다. 원인은 잘 모르지만, 의심되는 것은 다음과 같다.
  • 실행 가능한 war로 묶인 경우, 리눅스 기반 OS의 JDK 사용시 war 파일에 포함된 파일의 변경 여부를 확인하는 과정에서 실행 속도가 느려짐
자바8로만 해봤기 때문에 자바7이나 자바6에서 동일 증상이 발생하는지 여부는 모르지만, 어쨋든 war 파일에 포함된 파일을 탐색하는 과정에서 속도가 느려지는 것 같다. 왜 그런지 알고 싶지만, 여기까지 파고 있을 시간이 없어서 다음 과정을 진행했다.

실행 가능 war의 경우 JSP 변경 여부를 확인하는 과정은 별 의미가 없기 때문에, 임베디드 톰캣이 JSP 변경 여부를 확인하지 않도록 스프링 설정을 변경했다. 이때 사용한 것이 TomcatContextCustomizer이다. 아래 코드는 Jasper의 개발모드를 false로 설정하기 위해 사용한 스프링 설정의 일부이다.

import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;

@Configuration
public class ContainerConfig {

    @Bean
    public ServerProperties serverProperties() {
        return new ServerProperties();
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(){
        return new TomcatContainerCustomizer(tomcatJspServletConfig());
    }

    @Bean
    public TomcatJspServletConfig tomcatJspServletConfig() {
        return new TomcatJspServletConfig();
    }

    static class TomcatContainerCustomizer implements EmbeddedServletContainerCustomizer {

        private TomcatJspServletConfig tomcatJspServletConfig;

        public TomcatContainerCustomizer(TomcatJspServletConfig tomcatJspServletConfig) {
            this.tomcatJspServletConfig = tomcatJspServletConfig;
        }

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            ...
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory tomcatContainer = 
                      (TomcatEmbeddedServletContainerFactory) container;
                tomcatContainer.addContextCustomizers(tomcatJspServletConfig);
            }
        }

    }

    public static class TomcatJspServletConfig implements TomcatContextCustomizer {

        private ContainerJasperSetting jasperSetting;

        public TomcatJspServletConfig(ContainerJasperSetting jasperSetting) {
            this.jasperSetting = jasperSetting;
        }

        @Override
        public void customize(Context context) {
            // TomcatEmbeddedServletContainerFactory가 설정한 JspServlet의 이름이 "jsp"
            Wrapper jsp = (Wrapper)context.findChild("jsp");
            // JspServlet의 개발모드를 비활성화
            // 실제로는 application.properties 파일에 설정할 수 있는 프로퍼티 추가해서 구현
            jsp.addInitParameter("development", "false");
        }
    }

}

위 코드에서는 개발모드를 비활성화했지만, 실제 코드에서는 application.properties 파일에 "jsp.jasper.development" 프로퍼티의 값에 따라서 개발모드를 활성화/비활성화하도록 구현했다.

위와 같이 Jasper 엔진의 개발모드를 비활성화한 뒤에 실행가능한 war를 다시 실행해보니, 4초 간격으로 응답 시간이 느려지는 증상이 사라졌다.만세다!


+ Recent posts