banner-image

일주일동안 배운 것들을 정리해보는 시간을 가져보도록 하겠습니다.
정리하고보니 일이 너무 바빠서 개인 공부에 시간을 많이 투자하지 못했네요.

Erlang

얼랭을 배우기 시작했습니다. 전에 Akka를 써본 뒤로 액터 모델에 관심이 생겼는데요. 이에 대해 좀 더 배워보고, 함수형 언어도 배워보고자하여 얼랭을 익히기 시작했습니다.

개발하며 상기해야 할 것

  • 프로세스를 만들어서 병렬성을 잘 활용할 것
    • 본질적으로 병렬이라면 코드를 병렬로 만드는 걱정은 하지 않아도 된다.
  • 너무 많은 프로세스를 생성하지 말 것
    • pmap(F,L)은 병렬 프로세스 length(L)개를 생성한다.
  • 필요한 추상화에 대해 생각할 것
  • 메시지는 작게, 계산은 크게

Supervison tree

액터 모델에서 부모 액터가 자식 액터를 관리하는 관리자(Supervisor)가 되고, 전략을 작성하는 것과 같이 얼랭또한 supervisor process를 만들 수 있습니다.

one-for-one supervision

어떤 자식 프로세스가 실패하면, 그 프로세스만 supervisor에 의해 재시작됩니다.

all-for-one supervision

어떤 프로세스건 실패하면 (콜백 모듈에 있는 terminate/2를 호출하여) 모든 자식 프로세스가 죽음을 당하고, 자식 프로세스가 모두 재시작됩니다.

rest-for-one supervision

어떤 자식 프로세스가 실패하고 다시 시작될 경우, 의존하는 다른 프로세스도 같이 죽이고 다시 시작합니다. 단, 의존성이 있는 자식 프로세스만 죽이고, 의존성이 없는 다른 자식 프로세스는 그대로 유지할 수 있습니다.

init(...) ->
    {ok, { {RestartStrategy, Intensity, Period}, [ChildSpec]}}.
  • RestartStrategy : one_for_one 또는 all_for_one, rest_for_one으로 설정할 수 있습니다.
  • Intensity, Period : 각각 MaxRestarts, Time으로 생각할 수 있습니다. Period 내에 Intensity 이상을 재시작하면, 모든 워커 프로세스를 종료시키고 자기 자신도 종료합니다.

ChildSpec 정의

[Tag, {Mod, Func, ArgList},
    Reestart, Shutdown, Type, [Mod1]}
  • Tag : 나중에 자식 프로세스를 참조하는데 사용하는 애텀입니다.
  • {Mod, Func, ArgList} : Supervisor가 자식 프로세스를 실행할 때 사용할 함수 정의합니다.
  • Restart : 재시작 전략입니다.
    • permanent : 항상 재시작
    • transient : non-normal 종료 값으로 종료된 경우만 재시작
    • temporary : 결코 재시작되지 않음.
  • Shutdown : 자식 프로세스를 종료에 걸리는 최대 허용 시간. 이 시간을 넘으면 강제 종료합니다.
  • Type : 자식 프로세스의 종류를 정의합니다. worker or supervisor
  • [Mod1] : 자식 프로세스가 슈퍼바이저이거나 또는 gen_server 비헤이비어 콜백 모듈일 경우, 콜백 모듈의 이름입니다.

Mnesia DBMS

Erlang에서 기본적으로 제공하는 분산 통신 DBMS. 어떠한 형의 얼랭 데이터도 저장할 수 있습니다.

특징

  • 관계형/오브젝트 하이브리드 데이터 모델
  • DBMS 쿼리 언어, QLC(Query List Comprehension)가 애드온 라이브러리로 있음.
  • Persistence. 테이블은 RAM(속도가 중요한 경우)이나, DB에 저장할 수 있다(영속성이 필요한 경우)
  • Replication. 테이블은 무정지 기능을 제공하기 위해 다른 머신으로 복제할 수 있다.
  • Atomic transactions. 원자성 트랜색션
  • Location transparency. 위치투명성을 가짐.
  • 극단적으로 빠른 데이터 서치
  • Schema manipulation routines. DBMS를 멈추지 않고 런타임 중에 재조정할 수 있음.

왜 이름이 Mnesia인가

원래 이름은 Amnesia였다. 그런데 상사 중 한 명이 그 이름을 싫어함. 그의 주장은 이랬다. “Amnesia(건망증)라고 해서는 안 돼. 데이터베이스가 뭘 까먹어선 안 되잖아!” 그래서 A를 뗐고, 바로 그게 이름이 된 것
출처: Programming Erlang by Joe Armstrong

Android.mk로 생성한 프로젝트에 Mnesia 추가하기

문제점

프로젝트에서 바로 mnesia 관련 함수를 호출하면 bad_return 크래시 발생.
콘솔에서도 다음과 같이 미정의 상태를 확인할 수 있었습니다.

([email protected])1> mnesia:system_info(is_running).
** exception error: undefined function mnesia:system_info/1

ebin/<project_name>.appapplications 속성에 mnesia 를 추가할 경우 동작하는 것을 볼 수 있었습니다.

해결안

Makefile에 다음과 같이 추가합니다.

PROJECT_APP_EXTRA_KEYS = {included_applications, ['mnesia']}

Mnesia DB 디렉토리를 변경하고 싶다면

config/vm.args-mnesia dir '"/foo/bar/db/"' 과 같이 설정을 추가하여 mnesia DB 디렉토리를 설정할 수 있습니다.

application:set_env(mnesia, dir, DatabaseDir)

혹은 다음과 같이 애플리케이션 내부에서 동적으로 지정이 가능합니다.

Mnesia Table

Mnesia 테이블 생성

create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}
  • TabDef: 각종 옵션. 튜플 리스트 형태로 셋업합니다.
    • attributes : 컬럼 리스트 설정합니다. record를 활용하며 정의하고, record의 첫 번째 속성(attribute)이 primary key가 됩니다.
      • 예: {attributes, record_info(fields, <기존에 정의한 record>)
    • ram_copies, disc_only_copies, disc_copies
      • ram_copies : 노드에서 테이블을 RAM에 복제. 정기적으로 디스크에 덤프될 수 있지만, 기본적인 트랜잭션 단위로 디스크에 기록되지는 않습니다.
      • disc_only_copies : RAM이 아닌 디스크로에만 보관. RAM 기반 동작보다 훨씬 느립니다.
      • disc_copies : 테이블의 모든 업데이트는 실제 테이블에서 적용되고, 디스크에 로그 기록. 전체 테이블을 RAM과 디스크에 보관. 테이블에서 수행된 각 트랜잭션은 LOG 파일에 추가되고, RAM 테이블에 기록되는 형태. 쉽게 말하면 디스크 램 양쪽 모두 저장됩니다.
      • 예: {disc_copies, [NODE]}
create_table(users, [{attributes, record_info(fields, users)}, {disc_copies, [erlang:node()]}])

dirty_read, dirty_write

mnesia:read, mnesia:write 가 아닌 별도 트랜색션호출없이 바로 쉽고 빠르게 읽고 쓸 수 있습니다. 다만 트랜잭션 매니저를 우회하기 때문에 트랜잭션 매니저에 의한 오버헤드가 없지만 그만큼 보장없습니다.

dirty_write(Tab, Record) -> ok | exit({aborted, Reason})
dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}

dirty_read의 경우 조건문이 아닌 Key만을 활용함에 주의합니다.

gen_server 프로세스로 메세지 보내기

gen_server:call(ServerRef, Request) -> Reply
gen_server:call(ServerRef, Request, Timeout) -> Reply
gen_server:cast(ServerRef, Request) -> ok

ServerRef는 다음 중 하나일 수 있습니다.

  • Pid
  • Name : gen_server가 로컬에 등록되어 있을 경우 사용합니다.
  • {Name,Node} : gen_server가 다른 노드의 로컬에 등록되어 있을 경우 사용합니다.
  • {global,GlobalName} : gen_server가 전역으로 등록되어 있을 경우 사용합니다.
  • {via,Module,ViaName} : alternative process registry를 통해 등록된 경우 사용합니다.

call과 cast 차이

call은 동기 메세지, cast는 비동기 메세지입니다. cast로 보낼 경우, 대상 노드 또는 gen_server 프로세스가 존재하지 않을 경우, 해당 메세지는 무시됩니다.

Erlang ETS

What is ETS

Built-in term storage BIFs 인터페이스 모듈입니다. 많은 양의 데이터를 저장하고, 엑세스 시간을 일정하게 유지할 수 있습니다. 튜플 형태로 저장이 가능합니다. 각 테이블은 프로세스에 의해 생성되며, 프로세스가 종료되는 순간 삭제됩니다.

ETS 테이블 생성

ets:new(table_name, Options)
  • TableName : 애텀입니다.
  • Options
    • type : 테이블 타입입니다. set, ordered_set, bag, duplicate_bag이 있습니다. 기본적으로 set입니다.
    • access : 접근 권한입니다. public, protected, private이 있습니다.
    • named_table : 이 옵션이 있으면 테이블 식별자 대신 이름을 사용할 수 있습니다.
    • 기타 추가 옵션은 Document에서 확인할 수 있습니다.
ets:new(foo_table_name, [public, named_table])

ETC 테이블에서의 데이터 추가, 제거, 탐색

%% 데이터 추가
ets:insert(table_name, {Foo, Bar})

%% 데이터 제거
ets:delete(table_name, Key)
%% 이때, `Key` 는 데이터를 넣을 때 Tuple의 첫 번째 컬럼이 됩니다.

%% 데이터 탐색
ets:lookup(table_name, Key) -> []
%% 마찬가지로 Key는 데이터를 넣을 때 Tuple의 첫 번째 컬럼입니다.

기타

ets:select(Continuation) -> {[Match], Continuation} | '$end_of_table'

ets:select를 활용해 조건 탐색도 가능합니다.

레퍼런스