본문 바로가기

IT 살이/04. 기술 - 프로그래밍

위성어셈블리 반복 요청 최적화

1. 문제 제기

NTD타입의 스마트클라이언트 애플리케이션을 개발하면서, 서버로 어셈블리를 요청하는 과정을 모니터링하다 보면 다음과 같은 화면을 볼 수 있다.

리소스 어셈블리반복 요청

어셈블리를 하나 요청하면 그것과 관련해서 추가적인 요청이 여러번 반복된다. 이런 추가적인 요청이 로컬 PC에서만 일어난다면 모를까 원격에 있는 서버에 어셈블리를 요청할때마다 반복적으로 일어난다면 성능에 영향을 끼칠 수 있다. 이번에는 이런 현상이 왜 일어나는지 알아보고 방지하거나 최적화하는 방안에 대해 생각해 볼 것이다. 이런 현상은 .NET이 리소스를 관리하는 방식때문인데, 리소스 관리는 애플리케이션의 Globalizaion&Localization과 관련된 주제다.

2. Globalization(전역화) & Localization(지역화)

전역화(Globalization)는 여러 컬쳐(언어와 지역)에 있는 사용자를 위해 지역화된 사용자 인터페이스와 국가별 데이터를 지원하는 응용 프로그램을 디자인하고 개발하는 과정이다. 즉 동일한 애플리케이션이 영어권에 가서는 영어로, 프랑스어권에 가서는 프랑스어로 한국에서는 한국어로 UI가 출력되도록 한다는 것이다. 애플리케이션 자체를 영어, 프랑스어, 한국어 세가지로 개발하는 것이 아니라 응용프로그램 자체는 동일하고 UI에 출력되는 이미지나 문자열(라벨 컨트롤에 출력되는 텍스트를 생각한다) 그리고 날짜, 시간, 통화 같은 데이터의 포맷만 각 지역과 언어에 맞게만 변경해서 출력해 준다.
지역화(Localization)은 응용 프로그램 리소스를 설정된 현재의 컬쳐에 맞는 지역화된 버전으로 번역하는 과정이다. 예를 들어 현재의 컬쳐가 한국이라면 애플리케이션의 로고 이미지도 한글로 된 것으로 출력하고 조회 버튼의 텍스트도 "조회"출력한다. 또한 날짜도 yyyy/mm/dd" 포맷으로 변경하여 출력한다. 이것이 애플리케이션의 지역화이다.

애플리케이션의 지역화를 고려해서 개발자가 모든 컬쳐를 고려하는 식의 코딩을 해야 하는 것은 아니다.

if(영어권)
Logo = 영어로고이미지
else if(한국)
Logo = 한글로고이미지
...

개발자는 코딩은 동일하게 해도 된다. 다만 컬쳐에 맞는 UI 디자인이 변경될뿐이다. 코드상에서는 다음과 같이만 하면 된다.

Logo = 로고이미지

.NET의 리소스관리자는 이런 코드를 만나면 우선 현재 설정된 컬쳐를 보게 되고 그 컬쳐에 맞는 검색 경로에서 이미지를 검색해와서 출력해준다. 개발자는 여러 컬쳐에 맞는 이미지만 준비해서 리소스 관리자가 찾을 수 있는 위치에 두면된다.

3. 컬쳐 표현

컬쳐를 표현하는 방식은 다음과 같다.

언어코드[-국가(지역)코드]

예를 들면, "en-US"는 U.S 영어 컬쳐를 "en-AU"는 오스트레일이아의 영어 컬쳐를 말한다. 코드는 보통 2글자이다. 디폴트로 애플리케이션의 현재 컬쳐는 사용자 PC에 설정된 것을 따른다.
만약 프로그램상에서 특정 컬쳐에 대한 정보를 를 변경하고 싶다면 CultureInfo 인스턴스를 생성할 필요가 있다.

Thread.CurrentThread.CurrentUICulture = new CultureInfo("ko-KR");
Thread.CurrentThread.CurrentCulture = new CultureInfo("ko-KR");

첫번째 코드의 CurrentUICulture 속성은, 애플리케이션의 UI를 특정 컬쳐에 맞는 이미지를 출력하려고 할때 사용하는 설정이다. 두번째 코드의 CurrentCulture는 시간, 날짜, 통화를 특정 컬쳐에 맞게 출력하고 싶을때 사용하는 설정이다. 여기서는 리소스 관련 컬쳐 설정이 주제이므로 첫번째 설정과 관련이 있다.

4. .NET의 리소스 관리 모델

애플리케이션이 개발이 완료되고 배포될때,  설계, 개발시에는 예상치 못했던 특정 컬쳐에서도 서비스가 제공되어야 하는 경우가 있을 수 있다. 그렇다고 코딩을 다시 해서 재컴파일하는 작업을 할 수는 없는 일이다. 이런 경우 .NET에서 제공하는 리소스 관리 모델을 사용하면 쉽게 특정 컬쳐에 해당하는 리소스만 추가함으로써 문제를 해결할 수 있다. 이런 모델이 적용되려면 처음부터 지역화를 염두에 두고 비즈니스 로직을 담은 어셈블리와 리소스만 가지고 있는 어셈블리를 분리해서 개발해야 한다. .NET의 리소스 관리자는 현재 설정된 컬쳐에 해당하는 리소스 어셈블리를 찾아서 사용하게 된다. 이런 리소스 어셈블리를 위성 어셈블리(satellite assembly)라고 하는데, 코드는 없고 특정 컬쳐와 관련된 문자열이나 이미지 같은 리소스만 있다.

위성 어셈블리는 이름이 주는 느낌 그대로의 역할을 한다. 혼자서는 다른 로직을 수행할 수 없고 어떤 기본 어셈블리[각주:1]에서 필요로하는 자원을 공급해주는 역할을 한다. 이런 모델을 허브-스포크(hub-spoke) 모델이라고 한다.

허브-스포크 모델

그림에서 메인 어셈블리에는 주로 개발자가 작성하는 코드가 있다. 그리고 메인 어셈블리에도 리소스가 포함될 수 있는데, 컬쳐와는 상관없는 리소스들이다. 이 메인 어셈블리는 설정된 컬쳐의 위성 어셈블리가 없는 경우 최종적으로 리소스를 얻기위해서 소스를 검색하는 어셈블리로서 기본 리소스(default resources)라고 한다. 즉 "kr-KR"의 컬쳐가 적용되고 있는 애플리케이션이 실행되고 있을때, 리소스 관리자가 지정된 이미지를 찾기 위해서 "ko-KR" 컬쳐 관련 어셈블리를 지정된 위치에서 찾았지만 그 어셈블리를 발견하지 못했다면 또는 어셈블리는 찾았는데 원하는 이미지를 가지고 있지 않다면 최종적으로 메인 어셈블리에서 그 이미지를 검색한다.

앞에서 말한대로 이런 허브-스포크 모델을 사용하면 애플리케이션의 개발이 완료되고 배포까지 하고 나서도 새로운 컬쳐의 리소스가 필요하다면 그 컬쳐의 위성 어셈블리만 새로 만들어서 메인 어셈블리의 재컴파일없이도 배포할 수 있다는 것이다. 애플리케이션은 현재의 컬쳐에 적당한 위성 어셈블리만 로딩 또는 다운로딩해서 사용할 수 있다. 그러나 모든 컬쳐의 위성 어셈블리를 테스트하기위해서는 컬쳐별로 테스트환경을 구성해야 한다는 단점도 있다.

위성 어셈블리의 분리

허브-스포크 모델을 다시 그려보면 그림처럼 될 수 있다. 만약 ko-KR 관련 리소스만 있는 어셈블리, en-US 관련 리소스만 있는 어셈블리를 따로 만들어두고 컬쳐명과 동일한 이름의 디렉토리밑에 위성 어셈블리를 배포해두면 CLR이 필요한 어셈블리를 찾아서 로딩한다. 어셈블리를 두면 우리나라에서 애플리케이션을 실행시킬때와 미국에서 애플리케이션을 실행시킬때를 구분해서 해당 리소스 어셈블리를 선택적으로 사용할 수 있다는 것이다.

5. 특정 컬쳐의 리소스(Culture-specific resource) 관리

이제 Visual Studio.NET를 이용해서 특정 컬쳐의 위성 어셈블리를 만드는 방법을 알아본다. Visual Studio.NET의 프로젝트에서 윈폼을 하나 추가하면 기본적으로 하나의 Form1.resx 이라는 파일도 같이 추가된다.

기본 .resx 파일

이 .resx 파일에는 디자인 모드에서 폼에 추가되는 모든 자원에 대한 정보와 데이터가 기록된다. 이 파일은 XML 형식으로 되어 있으며 notepad.exe같은 텍스트 편집기로 열어보면 모든 기록이 텍스트로 되어 있는 것을 볼 수 있다. 이미지 같은 객체들의 내용은 바이너리 텍스트로 되어 있다.


<data name="button1.Locked"
type="System.Boolean, mscorlib,
Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089">
  <value>False</value>
</data>
<data name="button1.DefaultModifiers"
type="System.CodeDom.MemberAttributes, …">
  <value>Private</value>
</data>
<data name="button1.BackgroundImage"
type="System.Drawing.Bitmap, System.Drawing,
Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
mimetype="application/x-microsoft.net.object.bytearray.base64">
  <value>
       Qk1CBQAAAAAAADYAAAAoAAAAFgAAABMAAAABABgAAAAAAA
AAAADEDgAAxA4AAAAAAAAAAAAA/f39t7e3…
  </value>
  </data>

Form1.resx 파일은 컴파일을 하게 되면 메인 어셈블리(뒤에 설명한다. 지금은 우리가 코드를 작성해서 빌드하면 생성되는 보통의 어셈블리라고 생각하자)에 포함되는 기본 리소스를 가지고 있다. 폼에 기본 컬쳐(특정 컬쳐의 리소스를 찾을 수 없을 경우 사용하는 컬쳐를 말한다. 뒤에서 다시 설명한다)에 해당하는 UI 디자인을 한다.

기본 컬쳐의 윈폼

이제 ko-KR 컬쳐를 위한 UI를 만들어보자.

특정 컬쳐 리소스 추가

Form1 속성창에서 다음처럼 설정을 변경한다.

Localized->True
Language->한국어(대한민국)

이렇게 선택하면 오른쪽 그림처럼 ko-KR 컬쳐를 위한 리소스 정보가 저장될 .resx파일이 생성된다. 이때 국가(지역)코드가 없고 언어 코드만 있는 리소스 파일도 함께 생성된다. "한국어(대한민국)"을 선택한 다음부터의 디자인 정보는 이제 Form1.ko.resx,

Form1.ko-KR.resx 파일에 저장된다. 만약 "한국어(대한민국)" 대신 "한국어"를 선택했다면 ko에 해당하는 resx 파일만 생성한다.

ko-KR 컬쳐의 윈폼

이제 프로젝트를 빌드하면 다음과 같은 컬쳐별로 디렉토리가 생성되고 어셈블리가 하나씩 들어가 있는데 이것이 .resx 파일에 들어있던 데이터를 가지고 있는 위성 어셈블리이다.

메인 어셈블리, 위성 어셈블리

ResourceProject.exe에는 기본 컬쳐를 위한 Form1.resx 리소스가 임베딩되어 있다. 그리고 ko, ko-KR 폴더가 생성되어 있고 각각의 폴더에는 ResourceProject.resources.dll이 하나씩 들어있는데, 각 컬쳐에 해당하는 리소스가 들어있는 어셈블리이다.

여기에 폼 Form2를 하나더 추가해서 ko 컬쳐를 선택하면 Form2.ko.resx, Form2.ko.resx 파일이 생성된다.

지역화된 폼 추가

이것을 빌드하면 debug/ko/ResourceProject.resources.dll 및 debug/ResourceProject.exe 새로운 리소스가 추가된다. 위성 어셈블리는 컬쳐별로 관리되는 반면에 리소스 자체는 네임스페이스별로 관리된다. 만들어진 위성 어셈블리 파일명을 이루는 ResourceProject는 리소스가 사용된 클래스의 네임스페이스이다.

이제 애플리케이션 개발이 완료되고 이미 배포까지 마친 상태에서 기존의 폼에 새로운 컬쳐의 UI를 보여줄 필요가 있다면 해당 컬쳐의 .resx파일을 추가하고 빌드해서 기존 위성 어셈블리에 리소스를 추가한 다음 배포 디렉토리에 해당 컬쳐명과 동일한 폴더를 만든 후 그곳에 넣어두면 된다. 리소스 관리자는 지역화된 윈폼이 호출되면 우선 해당 위성 어셈블리를 로딩하고 필요한 리스소를 사용하게 될것이다.

이제 리소스 관리자(Resource manager)가 어떻게 이미지를 출력하기 위해 위성 어셈블리를 검색하는지 그 메커니즘을 알아보도록 하자.

6. 위성 어셈블리 반복 요청 배경

현재 컬쳐에서 사용할 수 있는 리소스는 복수개의 위성 어셈블리에 있을 수 있다. ko-KR 컬쳐에서 실행되는 애플리케이션은 하나의 리소스(버튼의 Text 속성 값)가 ko-KR 위성 어셈블리, ko 위성 어셈블리 그리고 메인 어셈블리에 모두 있을 수 있다. 이런 경우 리소스 관리자는 가장 구체적인 컬쳐의 위성 어셈블리부터 검색한다. ko-KR 위성 어셈블리에서 원하는 리소스를 검색하고 찾지 못하면 다시 ko 위성 어셈블리에서 찾는다. 거기서도 찾지 못하면 마지막으로 컬쳐 중립적인 기본 리소스를 검색한다.

리소스 관리자는 ResourceManager라는 클래스로 구현되는데, 리소스 관리자는 애플리케이션이 실행되는 현재 쓰레드의 CurrentUICulture 속성값을 기준으로 해서 리소스의 위성 어셈블리를 검색한다. 다음 예제 코드는 메인 어셈블리 LocalizedAssem.dll가 "ko-KR" 컬쳐의 위성 어셈블리를 검색하는 예이다.

AppBase\ko-KR\LocalizedAssem.resources.dll
AppBase\ko-KR\LocalizedAssem.resources\ LocalizedAssem.dll
AppBase\ko-KR\privatePath1\ LocalizedAssem.resources.dll
AppBase\ko-KR\privatePath1\ LocalizedAssem.resources \LocalizedAssem.resources.dll
AppBase\ko-KR\privatePath2\ LocalizedAssem.resources.dll
AppBase\ko-KR\privatePath2\ LocalizedAssem.resources\ LocalizedAssem.resources.dll

.exe 확장자를 갖는 어셈블리에 대해서 동일한 검색이 이뤄진다.

리소스가 사용된 어셈블리의 기본 네임스페이스(LocalizedAssem)를 근거로 위성어셈블리 파일명을 만들고 몇 단계의 검색을 거친다. 각 단계에서 적절한 리소스가 발견되면 그것을 사용하고 바로 검색을 멈춘다. 그렇지 않으면 다음 단계를 계속 진행한다. 리소스 관리자는 현재 첫번째는 현재 컬쳐명과 동일한 이름을 갖는 디렉토리에서 리소스 파일을 찾는다. 거기서 못찾게되면 두번째 줄처럼 리소스 파일이름과 동일한 이름의 디렉토리에서 찾는다. 계속 ~

여러분이 애플리케이션을 직접 제작한 경우 실제 검색 경로는 상황에 따라서 앞에서와는 다를 수 있다. 설정 파일 .config이 없는 경우는 Appbase 밑의 bin 폴더를 검색하는 경로가 출력될 수 있다. 또는 지역 코드가 없는 경우는 다른 검색 경로가 나올 지 모른다.

여기서 AppBase는 애플리케이션의 베이스 디렉토리[각주:2]값이다. ko-KR 하위 폴더를 검색하고 다시 ko 하위폴더를 검색하게 되는데, 검색할 수 있는 모든 경로를 찾지 못한다면 결국 메인 어셈블리의 기본 리소스를 참조하게 된다. 여기서도 찾지 못하면 어쩔 수 없이 이미지는 출력되지 못한다.

이런 식의 리소스 검색은 어떤 경우는 성능상의 문제가 될 수 있다. 특히 애플리케이션의 베이스 디렉토리가 원격 서버상의 경로(http://server/~~)로 설정되는 스마트클라이언트 애플리케이션의 경우는 메인 어셈블리를 요청할때마다 그 위성 어셈블리를 찾기 위해서 원격 서버로의 왕복이 계속해서 일어나게 된다. 따라서 위성 어셈블리에 대한 반복 요청을 최소화할 수 있는 방안이 필요하다.

7. 위성 어셈블리 반복 요청의 최적화 방안

이런 반복적인 요청을 어떻게 처리할 것인지에 대한 3가지의 방안이 있을 수 있다.

1) 아무 조치도 취하지 않는다.

위성어셈블리 반복 요청은 .NET 프레임워크의 의도된 거동이다. 한만디로 그 무시 무시한 "By design"이라는 말이다. 애플리케이션이 제 기능을 발휘하는 데는 아무 문제가 없다. 다만 성능이 안 좋게 나올뿐이다.

2). 사용자 정의 HTTP핸들러를 제작한다.

원격 서버에 DLL 파일 요청을 처리하는 사용자 정의 HTTP핸들러를 만든다. 핸들러에서는 리소스 파일에 대한 요청임을 확인하면 예를 들어 .resources.dll 끝나면 파일에 대한 요청인 경우는 0바이트의 더미 파일을 내려보냄으로써 더이상의 HTTP 요청이 올라오지 않도록 한다. 이 방안은 각 메인 어셈블리별로 리소스 파일에 대한 최소한 1번의 요청은 올라오게 된다.
이 방안의 단점이라면 추가적인 IIS 세팅과 web.config 파일 설정 그리고 HTTP핸들러이 구현된 어셈블리를 관리해야 한다는 것이다.

3) 애플리케이션의 컬쳐 정보 및 메인 어셈블리의 컬쳐 정보를 조정한다.

메인 어셈블리는 비록 컬쳐와는 상관없는 코드와 리소스만 있지만, 특정 컬쳐로 표시하게 되면 리소스 관리자는 이 설정에 따라 불필요한 어셈블리 검색을 하지 않게 된다.  이때 사용하는 어트리뷰트가 NeutralResourcesLanguageAttribute이다.

using System.Resources;
[assembly: NeutralResourcesLanguageAttribute("ko-KR")]

이 코드는 메인 어셈블리를 컬쳐 ko-KR로 표시하고 있다. 만약 현재 애플리케이션이 실행되는 쓰레드의 CurrentUICulture가 ko-KR로 메인 어셈블리의 설정된 컬쳐와 동일하다면 리소스 검색없이 바로 바로 메인 어셈블리의 리소스를 사용하게 된다.

using System.Resources;
[assembly: NeutralResourcesLanguageAttribute("ko")]

이렇게 국가(지역) 코드가 없는 컬쳐로 메인 어셈블리를 표시하면, 현재 애플리케이션이 실행되는 쓰레드의 CurrentUICulture가 "ko-KR"처럼 특정 지역이 표시된 경우만 디렉토리 검색을 하게 되고 국가 "ko"경우는 검색을 멈추게 된다.

현재 애플리케이션이 실행되는 쓰레드의 CurrentUICulture를 아예 다음처럼 없애버리는 방법도 있다.

Thread.CurrentThread.CurrentUICulture = new CultureInfo("");

이렇게 해 버리면 리소스 관리자의 위성 어셈블리 검색에 대한 시도가 아예 원천 봉쇄되버린다. 검색에 사용할 기준 경로를 만들어 낸 수가 없는 것이다.

4) 장단점

어셈블리나 애플리케이션의 어셈블리의 컬쳐를 조정하는 방법은 간단하긴 하지만 Globalization관점에서는 단점을 가진다. 특정 컬쳐로 고정되거나 또는 아예 컬쳐 지원 메커니즘을 사용할 수 없게 되어 버린다.

반면에 HTTP 핸들러를 사용하면 나중에 해외 지사를 확장한다든지 했을때 좀더 유연하게 대처할 수 있는 여지가 있다. 그러나 앞에서 말한 것처럼 관리와 설정에 대한 번거로움이 있다.

8. 결론

위성 어셈블리에 대한 반복적인 요청은 스마트클라이언트 애플리케이션에서는 성능에 큰 영향을 줄 수 있다. 정확히 말하면 애플리케이션의 베이스 클래스가 원격 서버상의 경로를 가리키는 경우에 문제가 된다. 따라서 ClickOnce는 문제가 되지 않는다. NTD와 혼합형(ClickOnce+NTD)의 배포 방식을 이용하는 애플리케이션에서 문제가 될 수 있다. 이런 경우 상황에 맞는 적절한 방안을 적용해야 한다.

  1. 메인어셈블리라고도 불린다 [본문으로]
  2. 스마트클라이언트 애플리케이션에서 애플리케이션 도메인(application domain)과 베이스 디렉토리(application base directory)에 대한 이해는 매우 중요하다. 이것에 대한 포스트는 곧 올리도록 할 것이다. [본문으로]