erlang 공부 : Building OTP Applications
- 프로그래밍 언어/erlang
- 2023. 12. 29.
참고
1. 왜 OTP Application이 필요한가?
여러 개발자가 서로 다른 방식의 스크립트를 작성하고, 그 스크립트를 이용해 Suepervision Tree를 실행하는 경우 Conflict이 발생할 수 있다. OTP Application은 이런 유형의 문제를 해결해준다.
- 디렉토리 구조
- 구성 처리 방법
- Dependency 처리 방법
- 환경 변수 및 Config 생성.
- Application 시작 / 중지
- Application을 종료하지 않고도 충돌을 감지하고 실시간 업그레이드를 처리할 수 있는 기능
OTP Application은 위 기능을 제공해준다.
2. OTP 디렉토리 구조 생성
ebin/
include/
priv/
src/
- ppool.erl
- ppool_sup.erl
- ppool_supersup.erl
- ppool_worker_sup.erl
- ppool_serv.erl
- ppool_nagger.erl
test/
- ppool_tests.erl
이전에 사용했던 코드를 다음 디렉토리 구조로 모두 옮긴다.
- ebin/ : 컴파일된 .beam 파일이 저장됨.
- include/ : erlang 헤더 파일 (.hrl)
- priv/ : 실행 파일, 기타 프로그램이나 어플리케이션을 시동하는데 필요한 특정 파일
- src : erlang 소스코드
- test/ : 테스트 파일만을 위한 디렉토리임.
각 폴더에는 다음 파일들이 저장된다. 테스트 파일은 test 디렉토리에 넣는데, 어플리케이션의 일부로 테스트 코드를 넣을 필요가 없기 때문이다. 초기에 소스 코드를 컴파일 하기 전에 비즈니스 로직이 정상적으로 동작하는지 확인하는데까지만 사용되기 때문에 test 디렉토리로 테스트 코드를 분리한다.
3. The Application Resource File
가장 먼저 해야할 일은 Application 파일을 추가하는 것이고, Application 파일의 목적은 다음과 같다.
- 어플리케이션이 무엇인지 / Start Point / End Point를 erlang VM에 알려줌.
3.1 Application File의 이름 / 저장방식
- 저장하는 방법은 두 가지가 있다.
- ebin/에 저장
- 이름 : <YOUR_APP_NAME>.app
- src/에 저장
- 이름 : <YOUR_APP_NAME>.aoo.src
Application 파일은 ebin/에 저장되어야 한다. 컴파일 된 파일이 ebin/에 저장되기 때문에 src/ 디렉토리에 <YOUR_APP_NAME>.app.src로 저장해두는 것도 가능하다. 컴파일되면 ebin/ 아래에 Application 파일이 함께 저장될 것이다.
예를 들어 위와 같은 구조로 application 파일을 넣어두어야 한다.
3.2 Application File의 구조
% ApplicationName은 Atom
{application, ApplicationName, Properties}.
기본적으로 Application File의 구조는 다음과 같다.
- application: 그대로 사용
- ApplicationName : 어플리케이션의 이름을 atom으로 표현
- Properties : 어플리케이션과 관련된 설정값들을 key, value 형식의 튜플로 관리함.
3.3 Application File Properties
위에서 말한 것처럼 어플리케이션 파일에서 사용하는 Properties는 Key / Value 형식의 튜플이다. 우선 Properties에 대한 전체적인 구조를 예시와 함께 살펴보면 다음과 같다. (http://erlang.org/documentation/doc-5.2/doc/design_principles/applications.html)
Properties =
[{description, String},
{vsn, String},
{id, String},
{modules, [{Mod1,Vsn1}, Mod2, {Mod3,Vsn3} .., {ModN,VsnN}]},
{maxP, Int | infinity},
{maxT, Seconds | infinity},
{registered, [Name1, Name2, ...]},
{applications, [Appl1, Appl2, .., ApplN]},
{included_applications, [Appl1, Appl2, .., ApplN]},
{env, [{Par1, Val1}, {Par2, Val2} .., {ParN, ValN}]},
{mod, {Mod, StartArgs}},
{start_phases, [{Phase, PhaseArgs}]}]}.
Properties는 위와 같이 사용할 수 있다. 각각의 의미는 대략 알 수 있는데 의미를 알기 어려운 것들만 추가적으로 살펴보면 다음과 같다.
- modules
- 어플리케이션이 시스템에 도입하는 모든 모듈의 목록을 포함함.
- 모든 모듈을 항상 최대 하나의 Application에서만 속함. 두 Application.app 파일에 하나의 모듈이 동시에 존재할 수 없음.
- 표준 OTP 구조를 사용중이고, 빌드 도구를 사용하는 경우 이 작업이 자동으로 처리됨.
- Application의 Depndency를 확인해서 다른 Application과 충돌은 없는지, 필요한 Dependency가 있는지 확인함.
- registred: 이 애플리케이션에 사용된 등록된 프로세스의 모든 이름을 나열함.
- MaxT : 어플리케이션이 실행될 수 있는 최대 시간임. 이 시간 이후에는 종료됨. 기본값은 무한대임.
- MaxP : 동시에 가용 가능한 프로세스 개수. 기본값은 무한대.
- applications
- 이 어플리케이션이 의존하는 다른 어플리케이션을 의미함.
- Erlang의 어플리케이션 시스템은 어플리케이션을 시작하기 전에 Dependency 어플리케이션이 로드 / 시작되었는지를 확인함.
- 모든 Application은 stdlib, kernel application에 의존함. (Default)
- mod
- Application Behaviour를 사용하는 어플리케이션의 콜백을 등록함.
- 이게 등록되면 OTP가 어플리케이션을 시작할 때 Mod:start(StartArgs)를 호출하도록 함.
- include_applications: 이 어플리케이션에 포함된 어플리케이션 목록이다. application_controller에 의해 로드는 되지만, 시작되지는 않음.
- 만약 include_application의 프로세스를 생성하고 싶다면 include_application의 슈퍼바이저에게 해당 프로세스를 등록해야 함.
4. 이전 Pool을 OTP Application으로 전환하기
이전에 사용했던 Pool 코드는 OTP Application이 아니었다. 여기서는 Pool 코드를 OTP Application으로 변경하는 작업을 실행한다.
4.1 디렉토리 구조 변경
ebin/
include/
priv/
src/
- ppool.erl
- ppool_serv.erl
- ppool_sup.erl
- ppool_supersup.erl
- ppool_worker_sup.erl
test/
- ppool_tests.erl
- ppool_nagger.erl
디렉토리 구조는 다음과 같이 변경한다.
- ppool_nagger는 test로 이동시킨다. 이 모듈은 프로덕션에서 사용될 코드가 아니다.
- ppool이 정상적으로 동작하는지 E2E 테스트를 하기 위해서 사용된 코드이기 때문에 test/ 디렉토리로 이동시켰다.
4.2 EmakeFile 생성
% EmakeFile
{"src/*", [debug_info, {i,"include/"}, {outdir, "ebin/"}]}.
{"test/*", [debug_info, {i,"include/"}, {outdir, "ebin/"}]}.
컴파일에 사용하도록 다음과 같이 EmakeFile을 생성한다.
EmakeFile을 다음과 같이 erlang 프로젝트의 Root 디렉토리에 복사해둔다.
4.3 ebin/에 app 파일 생성
%% ebin/ppool.app
{application, ppool, [
{description, ""},
{vsn, "1.0.0"},
{registered, [ppool]},
{applications, [
kernel,
stdlib
]},
{mod, {pool_api, []}},
{env, []},
{modules, [sup_of_pool_sup, pool_api, pool_server, pool_sup, pool_worker_sup]}
]}.
다음과 같이 ebin 디렉토리 아래에 app 파일을 생성한다.
5. Application Behaviour
위 작업을 통해 OTP Application을 위한 기초 작업이 완료되었다. 그런데 OTP Application을 시작하려면 Application으로 사용할 모듈을 하나 구현해야한다. behaviour(application)의 콜백을 구현해야 OTP Application을 시작할 수 있다.
이전에 몇번 언급되었지만, OTP의 behaviour는 '일반적인 부분' / '비즈니스 로직'을 분리하고, '일반적인 부분'을 담당하는 콜백을 의미한다.
5.1 Application Behaviour의 동작방식
- VM이 처음 시작될 때 마다 Application Controller 프로세스가 시작됨. (이름은 application_controller)
- Application Contrller는 다른 모든 Application을 시작함. Application Controller는 erlang VM에서 동작하는 모든 Application의 Supervisor로 볼 수 있음.
- application:start()를 이용해 Application을 시작하면 Application Controller가 Application Master를 시작함.
- Application Master는 Application의 최고 Supervisor와 Application Controller의 중재자 역할을 함.
5.2 behaviour 콜백 구현
Application behaviour의 callback은 두 가지가 존재한다.
- start/2 : YourMod:start(Type, Args) 형식을 취함. Args는 app 파일에서 제공됨. Application의 최고 Supervisor Pid만 반환하면 됨.
- stop/1 : State를 가지는데, 이 State는 start/2가 반환한 값임. 즉 Pid임.
application behaviour를 구현한 모듈은 application:start()를 이용해서 어플리케이션을 시작할 수 있다. application:start(AppName)으로 어플리케이션을 시작하면, 그 모듈의 start/2가 호출된다. 이 때, start/2는 해당 OTP Application을 관리할 최상위 Supervisor를 시작하고, 슈퍼바이저의 Pid만 반환하면 된다.
6. Pool에 Application behaviour 적용하기
위에서 Application Behaviour에 대해서 설명했다. 이제 pool을 OTP Application으로 바꾸기 위해서 application behaviour를 적용하고 콜백을 구현해본다.
6.1 코드 적용하기
% ppool.erl
-export([start_link/0, stop/0, start_pool/3,
run/2, sync_queue/2, async_queue/2, stop_pool/1]).
start_link() ->
ppool_supersup:start_link().
stop() ->
ppool_supersup:stop().
- ppool.erl을 살펴보면 다음 코드가 있는 것을 볼 수 있다. 여기서 ppool_supersup:start_link()는 ppool 슈퍼바이저 트리의 최상위 슈퍼바이저를 시작하는 것이다.
- application은 시작하면, Application Controller 프로세스가 Application Master를 생성하고, Application Master는 Application Controller와 Application의 최상위 슈퍼바이저를 연결해준다. 즉, VM의 Supervisor 트리 아래에 두는 것이다.
아무튼 위 코드를 아래와 같이 고치면 된다.
-behaviour(application).
-export([start/2, stop/1, start_pool/3,
run/2, sync_queue/2, async_queue/2, stop_pool/1]).
start(normal, _Args) ->
ppool_supersup:start_link().
stop(_State) ->
ok.
6.2 적용한 코드 실행해보기
$ erl -make
% pa = path add임.
% -pa 다음에 나오는 디렉토리들은 Erlang이 모듈을 로드할 때 검색하는 경로에 포함됨.
$ erl -pa ebin/
1> make:all([load]).
2> application:start(ppool).
3> ppool:start_pool(nag, 2, {ppool_nagger, start_link, []}).
{ok,<0.142.0>}
4> ppool:run(nag, [make_ref(), 500, 10, self()]).
{ok,<0.146.0>}
- 컴파일 한 후에 얼랭 쉘에 접속한다.
- 접속후 application:start(ppool)을 한다.
- 이 함수는 Application Controller가 새로운 Application Master를 생성해서 Application의 최상위 Supervisor를 시작하고 연결하도록 한다. 즉, application이 새로 시작되는 것을 의미한다.
6.3 application:start()의 여러 사용 방법
Application도 Supervisor처럼 여러 타입이 존재한다.
- temporary
- transient
- permanent
기본적으로 다음 타입이 존재하고, 자세한 것은 문서를 찾아보자. 일반적으로 application:start(AppName)을 하는 경우 temporary 타입으로 시작된다. 만약 다른 타입으로 시작하고 싶다면 다음 코드로 실행하면 된다.
application:start(AppName, temporary).
application:start(AppName, transient).
application:start(AppName, permanent).
7. 라이브러리용 Application
단순히 라이브러리로 사용할 Application은 프로세스를 띄우지 않고 Supervisor 트리를 이용하지 않기 때문에 별다른 최상위 Supervisor가 없다. 이런 경우에는 어떻게 작성하면 될까? 결론부터 이야기하면 {mod, {Module, Args}}를 app 파일에서 제거하면 된다.
{application, stdlib,
[{description, "ERTS CXC 138 10"},
{vsn, "%VSN%"},
{modules, [array,
...
gen_event,
gen_fsm,
gen_server,
io,
...
lists,
...
zip]},
{registered,[timer_server,rsh_starter,take_over_monitor,pool_master,
dets]},
{applications, [kernel]},
{env, []}]}.
위는 stdlib이라는 application으로 일반적으로 라이브러리로 사용된다. 프로세스를 따로 띄우지 않는 라이브러리 모듈인데, 위의 App 파일에서 볼 수 있듯이 {mod, {Module, Args}}이 존재하지 않는 것을 알 수 있다.
'프로그래밍 언어 > erlang' 카테고리의 다른 글
Erlang 공부 : Distribunomicon (0) | 2024.01.02 |
---|---|
Erlang : ETS Table (0) | 2023.12.31 |
인텔리제이 erlang 디버그 모드 설정 (0) | 2023.12.29 |
erlang 공부 : Rage Against The Finite-State Machines 2부 (0) | 2023.12.27 |
erlang 공부 : Rage Against The Finite-State Machines 1부 (0) | 2023.12.27 |