본문 바로가기

Project 자료수집

메이븐을 이용한 정적 파일 배포

출처 : http://helloworld.naver.com/helloworld/1242

NHN Business Platform 쇼핑서비스개발팀 오영은

웹 애플리케이션을 잘 개발하는 것 못지 않게 잘 배포하는 것도 중요합니다. 어떻게 배포하느냐에 따라 장애를 줄일 수도 있으며, 사용자가 성능 향상을 체감하게 할 수도 있습니다. 이 글에서는 지식쇼핑 서비스에서 사용하는 정적 파일 배포 방법을 소개합니다.

  • 정적 파일 배포의 어려움

    CSS 파일이나 자바스크립트 파일과 같은 정적 파일은 브라우저에 캐시되도록 하면 사용자 입장에서는 속도 향상을 체감할 수 있고 서버는 리소스 부담을 줄일 수 있다. 그런데 정적 파일의 내용이 변경되면 브라우저가 캐시된 기존 파일을 사용하지 않고 새로운 파일을 내려받도록 해야 한다. 가장 많이 사용하는 방법은 common.css?t=20110405처럼 정적 파일의 이름에 쿼리스트링 형태로 타임스탬프를 추가하는 것이다. 이 타임스탬프 값으로는 빌드 시각이나 웹 애플리케이션 서버의 시작 시각, 또는 저장소의 리비전 번호를 사용할 수 있다. 그런데 이때 다음과 같은 문제가 발생할 수 있다.

    122111_0532_1.png

    그림 1 배포 전 서버와 배포 후 서버

    예를 들어 웹 애플리케이션을 배포하는 중에 사용자가 웹 페이지에 접속하여 HTML 파일은 A 서버(배포 후)에서 가져오고 CSS 파일은 B 서버(배포 전)에서 가져오면 새로운 HTML 파일과 기존 CSS 파일을 사용하게 된다. 그러면 페이지가 제대로 보이지 않거나 오동작이 발생할 수 있다.

    이런 문제를 방지하려면 새로 배포하는 파일의 이름을 변경하여 기존 HTML 파일은 기존 CSS 파일을 참조하고 새로운 HTML 파일은 새로운 CSS 파일을 참조하도록 해야 한다. 그리고 웹 서버 장비를 여러 대 사용할 때에는 일부 서버에만 CSS 파일이나 자바스크립트 파일이 배포되는 경우를 막기 위해서 정적 파일을 미리 배포하기도 한다.

    위 방법을 사용하면 정적 파일을 배포할 때 발생하는 대부분의 문제점을 예방할 수 있지만, 웹 페이지에서 참조하는 정적 파일의 개수가 많으면 성능을 저하시킬 수 있다. 파일을 캐시하지 않은 경우에는 파일을 매번 내려받아야 하고, 캐시한 경우에는 매번 서버에 접속해서 "304 Not Modified"를 수신하기 때문에 DNS resolve 비용과 소켓 연결 비용이 발생한다. 파일을 합쳐서 파일의 개수를 줄여도 불필요한 공백 문자나 긴 변수 이름 등으로 인해 많은 데이터를 전송해야 한다. 특히 웹 서버에 정적 파일을 배포하면 HTTP 요청 메시지에 포함되는 쿠키 역시 전송되므로 요청 메시지의 크기도 커진다.

    모든 개발 과정에서 작성하는 정적 파일을 마치 한 명이 작성한 것처럼 체계적으로 관리할 수 있다면 좋겠지만 관리 비용 때문에 오히려 비효율적일 수도 있다. 따라서 배포 과정을 최대한 자동화하는 것을 지향하며, 자동적이고 효율적인 배포를 위해 다음과 같은 규칙을 지켜야 한다.

    • 타임스탬프 값을 쿼리스트링에 추가한다.
    • CSS 파일과 자바스크립트 파일의 버전을 관리한다.
    • 파일의 개수를 줄인다.
    • 파일 내용을 최적화(minify)한다.
    • CSS 파일과 자바스크립트 파일을 cookie free domain으로 제공한다.
  • 아파치 웹 서버 설정을 이용한 최적화

    아파치 웹 서버의 deflate 모듈을 사용해서 정적 파일을 압축하여 전송한다. 아파치 설정 파일에 다음과 같이 deflate 모듈 설정을 추가한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # deflate 모듈 추가
    LoadModule deflate_module modules/mod_deflate.so
      
    # 압축 레벨
    DeflateCompressionLevel 1
      
    # MIME type에 따른 압축 설정
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml text/javascript
      
    # MIME type 추가
    AddType text/javascript .js
    AddType text/css .css

    그리고 브라우저가 정적 파일을 캐시할 수 있도록 expire 모듈도 추가한다.

    1
    2
    3
    4
    5
    6
    7
    # expires 모듈 추가
    LoadModule expires_module modules/mod_expires.so
      
    ExpiresActive On
      
    # 기본 expires 설정을 1년으로
    ExpiresDefault "access plus 1 years"
  • 정적 파일 최적화 Q&A

    다음과 같이 자바스크립트 파일을 저장할 위치를 /web/js라고 가정하고 웹 페이지에서 사용할 main.js 파일과 detail.js 파일을 /web/js 디렉터리 아래에 둔다고 가정하자. 버전 관리를 위해서 각 자바스크립트 파일에는 버전을 표시한다(예: main_v1.js, detail_v1.js).

    122111_0532_2.png

    그림 2 디렉터리 구조

  • Q. 각 웹 페이지에서 버전과 타임스탬프를 기술하지 않고 어떻게 자바스크립트 파일을 추가하는가?

  • 웹 애플리케이션을 로드할 때 /web/js를 포함한 하위 디렉터리 파일들을 스캔하여, 가장 버전이 높은 파일의 목록을 생성한 뒤, 다음과 같이 커스텀 태그를 이용하여 파일의 경로를 표시한다.

    1
    2
    <link:js prefix="/js/main.js">
    </link:js>

    다음은 실제로 HTML에 출력되는 내용이다.

    1
    <script type="text/javascript" src="/js/main_v1.js?t=2011040512"></script>

    만약 하위 호환이 되지 않는 새로운 내용을 추가하거나 변경하려면 버전을 올린다(예: main_v1.js ? main_v2.js).

  • Q. 이렇게 하면, 배포 시에 문제는 없나?

  • 배포 후 웹 서버에서는 새로운 웹 페이지가 새로운 자바스크립트 파일(main_v2.js)에 연결되고, 배포 전 웹 서버에서는 기존 웹 페이지가 기존 자바스크립트 파일(main_v1.js)에 연결된다. 정적 파일이 미리 배포되었다면, 모든 웹 서버에는 main_v1.js 파일과 main_v2.js 파일이 존재하므로, 배포 중이라 하더라도 문제가 발생하지 않는다.

  • Q. 웹 페이지 개발 기간에는 정적 파일 역시 개발 버전(외부 URL)을 사용하게 되는데 이런 경우는 어떻게 처리하는가?

  • 다음과 같이 커스텀 태그를 수정하여 개발 용도의 URL을 기술한다.

    1
    2
    <link:js prefix="/js/main.js" devonlyurl="http://svn.nhndesign.com/service/main_20110404.js">
    </link:js>

    개발 중에는 웹 애플리케이션개발 중에는 devOnlyUrl을 사용하고 개발 완료 후에는 가장 버전이 높은 main.js 파일을 사용하도록 설정한다. 물론, 개발 중 여부에 대한 정보는 설정할 수 있어야 한다.

  • Q. 쿠키를 사용하지 않는 도메인에서 CSS 파일이나 자바스크립트 파일을 제공할 때 문제는 없는가?

  • 자바스크립트 파일 내에서 사용자 쿠키 값을 이용하는 경우, 해당 자바스크립트 파일은 웹 서버 내의 경로에 존재해야 한다. 이를 위해 커스텀 태그에서 추가로 needCookie와 같은 설정이 필요하다.

    1
    2
    <link:js prefix="/js/cookie.js" needcookie="true">
    </link:js>

    지금까지 정적 파일 버전 관리와 함께 개발 중인 파일에 대한 기술 방법에 대해서 이야기했다. 이제 파일의 개수를 줄이기 위해 main.js 파일과 detail.js 파일을 묶어 하나의 core.js 파일을 생성하는 방법을 알아보자. 설정 파일은 다음과 같이 작성한다.

    1
    2
    3
    4
    5
    6
    7
    <!--?xml version="1.0" encoding="UTF-8"?-->
    <compression>
    <file target="/js/core.js">
    <include>/js/main</include>
    <include>/js/detail</include>
    </file>
    </compression>
  • Q. 생성된 파일은 어떻게 버전을 관리하나?

  • main_v1.js 파일과 detail_v1.js 파일을 묶어 core_v1.js 파일을 생성했다면, main_v1.js 파일과 detail_v1.js 파일에 변경 사항이 없으면 core_v1.js라는 이름을 유지해야 한다. main_v1.js 파일이나 detail_v1.js 파일의 내용이 변경되거나 파일의 버전이 변경되면 core_v1.js 파일도 core_v2.js 파일로 변경해야 한다. 이를 위해서는 생성할 파일을 구성하고 있는 각 파일의 이름, 크기, checksum 등의 정보를 관리해야 한다.

    1
    2
    3
    4
    version=1
    encoding=UTF-8
    file./js/main_v1.js=945,UTF-8,ac3fecacd0a6ecf74a0c711180582a8c
    file./js/detail_v1.js=22572,UTF-8,ee2565f0938bfd1baa21c73afa633c81

    이제 합친 파일을 최적화(minify)할 차례이다. 최적화와 관련된 라이브러리는 많지만 여기에서는 가장 많이 사용하는 YUI Compressor를 사용한다.

  • Q. 파일 생성이나 최적화는 런타임에 수행할 것인가, 빌드 타임에 수행할 것인가?

  • 정적 파일을 CDN 또는 별도의 서버에서 제공하려면 빌드 타임에 수행하는 것을 권장한다. 이를 위해서 메이븐 플러그인 형태로 파일 생성 및 최적화 기능을 구현했다. 다음은 pom.xml 파일의 예이다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <build>
    <plugins>
    <plugin>
    <groupid>com.naver.shopping.maven</groupid>
    <artifactid>maven-compress-plugin</artifactid>
    <version>1.0.12</version>
    <configuration>
    <webdirectory>web</webdirectory>
    <fileencoding>UTF-8</fileencoding>
    <configlocation>web/WEB-INF/compression.xml</configlocation>
    <repository>http://xxx.shopping.naver.net/static/</repository>
    </configuration>
    </plugin>
    </plugins>
    </build>
  • BDS를 이용한 배포

    NHN에서는 자체적으로 개발한 BDS라는 빌드 배포 시스템을 사용하여 웹 애플리케이션을 배포한다. 다음 그림은 기존 배포 방식을 간략하게 나타낸 것이다.

    122111_0532_3.png

    그림 3 BDS를 이용한 배포

    다음 그림은 기존 배포 방식에 CSS 파일과 자바스크립트 파일 최적화 과정을 추가한 것이다.

    122111_0532_4.png

    그림 4 CSS, 자바스크립트 최적화를 위한 배포

  • 압축 파일 생성: 메이븐을 이용하여 빌드할 때, compression.xml 파일을 읽어 압축 파일을 생성한다.
  • 버전 파일 생성: compression.xml 파일에 기술된 정보를 이용하여 버전 파일을 생성한 뒤, 버전 파일 저장소(pom.xml에 기술된 repository)에 저장한다.
  • CSS, 자바스크립트 파일 복사: BDS의 PostScript 기능을 이용하여 CSS 파일과 자바스크립트 파일을 CDN 또는 cookie free domain 서버에 복사한다.
  • 설정 및 사용 방법

  • compression.xml 파일을 생성하여 web/WEB-INF 디렉터리에 저장한다.
  • Lucy를 사용하는 웹 프로젝트는 다음과 같이 Lucy 플러그인을 설정한다.
    1
    2
    3
    4
    5
    <lucy:plug-in name="compress" class="com.naver.shopping.common.compress.web.CompressPlugIn">
    <lucy:param name="configLocation" value="/WEB-INF/compression.xml">
    <lucy:param name="devOnly" value="false">
    <lucy:param name="baseUrl" value="http://static.shopping.naver.net/f">
    </lucy:param></lucy:param></lucy:param></lucy:plug-in>

    Spring MVC 기반의 웹 프로젝트는 다음과 같이 Bean을 등록한다.

    1
    2
    3
    4
    5
    <bean class="com.naver.shopping.common.compress.web.CompressBean">
    <property name="configLocation" value="/WEB-INF/compression.xml">
    <property name="devOnly" value="false">
    <property name="baseUrl" value="http://static.shopping.naver.net/f">
    </property></property></property></bean>
  • pom.xml 파일에 메이븐 플러그인 설정을 추가한다.
  • jsp 파일에 다음과 같이 기술한다.
    1
    2
    3
    4
    <link:js prefix="/js/lib.js">
    <link:js prefix="/js/ac.js" needcookie="true">
    <link:js prefix="/js/notexists.js">
    </link:js></link:js></link:js>

    위와 같이 기술한 내용은 다음과 같이 변환되어, 정적 파일 중 쿠키가 필요한 자바스크립트 파일은 웹 서버에서 서비스하고, 그렇지 않은 CSS, 자바스크립트 파일은 cookie free domain 또는 CDN에서 서비스한다.

    1
    2
    3
    <script type="text/javascript" src="http://static.shopping.naver.net/f/js/ lib_v1.js"></script>
    <script type="text/javascript" src="/js/ac_v1.js"></script>
    <script type="text/javascript" src="http://static.shopping.naver.net/f/js/ notexists_v2.js?t=2011040512"></script>

    /js/notexists.js 파일은 compression.xml 파일에 정의되어 있지 않은 파일이다. 이런 경우에는 /js 디렉터리 아래 notexists로 시작하는 가장 높은 버전의 파일의 이름과 타임스탬프 값을 생성하여 반환한다.

  • 마치면서

    지금까지 간략하게나마 지식쇼핑 서비스에서 사용하는 정적 파일 배포 방법을 설명했다. 이런 방식으로 정적 파일을 관리하는 것은 크게 새로운 방법은 아니다. 그러나 사내에서 담당자끼리만 공유하던 내용을 이렇게 지면에 실은 것을 계기로 많은 사람들이 나은 방법을 모색하는 기회가 되었으면 한다.



  • helloworld_%EA%B4%80%EB%A6%AC%EC%9E%90%EA%B3%84%EC%A0%95%EC%82%AC%EC%A7%84%EB%B0%B0%EA%B2%BD.gif
    NBP 쇼핑서비스개발팀 오영은
    "아는 것이 힘이다(scientia est potentia)" - 프란시스 베이컨. 익숙한 것에 머무르지 않고, 더 나은 것에 대한 동경으로, 무지(無知)의 두려움으로부터 헤어나기 위해, 이제 겨우 한 발자국씩 떼고 있습니다.

    Tag :



    'Project 자료수집' 카테고리의 다른 글

    OPKG(Open PacKaGe management system) 란?  (0) 2014.03.12
    배포 자동화 (Continuous Deployment)  (0) 2014.02.03
    tftp.java  (0) 2014.01.13
    학원 무료교육 - spring  (0) 2014.01.09
    spring 꼭 봐야함  (0) 2014.01.09