3. 분산 언어(Distribued Langauge)

분산 언어를 다루기 전 먼저 분산시스템에 대해 알아보자.

3.1. 분산 시스템

분산 시스템은 프로그램상 작업을 여러 시스템에서 분산시켜 처리하는 시스템을 의미한다. 일반적으로 처리 속도를 향상 시키는데 활용된다.

3.2. 분산 시스템이 어려운 이유

크게 3가지 이유가 있으며, 이에 관련된 CAP 이론이 존재한다.

3.2.1. Patial Failure(부분 실패)

로컬 프로세스는 OS안에서 관리된다. 특정 프로세스가 강제 종료되더라도 OS는 모든 프로세스에 대한 정보를 알고 있으며, IPC를 수행하는 중 장애가 발생하더라도 OS에 쿼리하여 정보를 얻을 수 있다.

하지만 여러 컴퓨터에 분산된 프로세스는 신뢰할 수 있는 중앙 관리자가 존재할 수 없다. 로컬 프로세스와 달리 네트워크를 활용하기 때문에 네트워크 파티셔닝으로 인해 서로간 정보가 단절될 수 있기 때문이다. 여러가지 네트워크상의 이유로 정상적인 메시지가 유실되거나 순서가 뒤바뀔 수 있으며, 실패 메시지 조차 유실될 수 있다. 즉 부분 실패가 발생했을때 원인조차 알기 어렵다.

따라서 부분 실패는 분산 시스템 설계시 반드시 잘 처리되어야 한다. 몇몇 방법론이 존재하는데 그중 하나는 Dataflow model 이다. Dataflow 모델에서는 실패를 대비하여 전체 계산 절차를 기억하고 있다. 또 다른 방법은 Two phase commit을 사용하는 것이다. 이 방법에서는 상태 변경 직전에 관리자에게 모든 노드가 준비되었는지 물어보고, 모두 준비 되었다면 변경을 실행한다. 만약 준비되지 않았다면 어떠한 변경도 수행하지 않는다.

또 다른 방법은 복제본(replication)을 미리 만들어 놓고 실패시 복제본이 작업을 받아 처리하도록 하는 것이다. 또 다른 방법은 각 상태에 대한 스냅샷(Snapshot) 또는 객체을 잘 저장해놓고 불러와서 다시 계산을 수행하는 방법이 있다.

3.2.2. Consistency(일관성)

로컬 환경에서 각 프로세스는 매우 빠르게 통신할 수 있으며, 결과를 빠르게 받아볼 수 있다. 비록 데이터 레이스 등 동시 처리에 여러가지 문제가 발생할 수는 있지만, Lock등 비교적 간단한 기법으로 해결할 수 있다.

분산 시스템의 경우 여러 시스템이 하나의 일관된 상태를 유지하기 매우 어렵다. 신뢰할 수 있는 중앙 관리자와 부분 실패 문제로 인해 모든 시스템이 같은 상태를 유지하기 어렵다. 그래서 일관성 문제 역시 분산 시스템 설계시 반드시 다뤄져야한다.

이를 해결하기 위해 사용되는 흔히 사용되는 방법은 메시지 큐 방식이다. 각 프로세스에 큐를 할당하고 쌓인 메시지는 순차적으로 1개씩 처리된다. 이 순차적으로 처리되는 특성 덕분에 일관성이 깨질 위험이 거의 없다. 하지만, 이러한 큐 방식도 네트워크 파티셔닝 문제를 해결할 수는 없다.

현재는 느슨한 일관성을 유지하는 방법이 사용되고 있다.

3.2.3. Latency(지연시간)

네트워크 활용으로 인해 지연시간이 발생하므로 분산 시스템은 좋은 성능을 갖기 어려우며 비결정성을 띄게 된다. 그리고 특정 타이밍을 요하는 기능을 구현할 수가 없다. 또한 메시지를 받지 못했을때 실패 메시지인지 그저 메시지 전송이 느린건지 구분할 수 없다.

지연시간으로 인한 성능 문제를 해결하기 위해 비동기 프로그래밍이 활용될 수 있다. Future나 Promise를 이용해 요청을 기다리는 동안에도 다른 작업을 하여 성능문제를 해결할 수 있다.

3.2.4. CAP

위와 같은 세가지 문제는 서로 독립적이지 않고 연관되어 있다. 그리고 어떤 하나의 문제를 해결하는 솔루션은 다른 문제에 악영향을 끼칠 수 있다. 예를들어 일관성을 맞추기 위해 동기화를 수행 하다보면 시간이 지연된다. 반대로 지연시간을 줄이기 위해서는 일관성을 깨뜨릴 수 밖에 없다.

이러한 관계성에 대해 정립한 것이 CAP 이론이다. 어떤 분산 시스템이든 Consistency, Availability, Tolerance to Partitioning 중 2가지만 충족할 수 있다는 것이다. 하지만 CRDT같은 이론이 등장하면서 반드시 2개만 충족한다는 것은 의미가 많이 퇴색되었다.

  • Consistency: 모든 노드가 가장 최근의 읽기나 실패에 대한 정보를 아는 것
  • Availability: 어떤 요청을 해도 일단 정상적인 응답을 받는 것 (최신 정보가 아니더라도)
  • Tolerance to Partitioning: 네트워크가 단절되더라도 시스템이 정상동작하는 것

3.3. 분산 프로그래밍 언어/시스템 분류

크게 분산 프로그래밍 언어에는 3가지 종류로 나눠진다. DSM(분산 공유 메모리, Distribued Shared Memory), Actor model, Dataflow model 이다.

3.3.1. DSM(Distributed Shared Memory)

특정 프로세스의 가상 메모리를 여러 컴퓨터에 분산 시켜 배치하는 방법이다. 이 방식의 장점은 로컬프로그래밍 하듯이 프로그래밍을 할 수 있다는 것이다. 주의할 점은 시스템 콜이 정상적으로 구현되어야 한다는 것이다. Linda등의 언어가 있다.

3.3.2. Actor/Object model

액터라 불리는 메시지 큐를 갖는 객체를 활용하여 프로그래밍 하는 방법이다. 메시지 기반으로 통신하며 DSM 처럼 상태를 공유하지 않는다. 메시지에 로컬, 원격에서 발송되었을 수 있지만 프로그래머 입장에선 투명성을 가지고 있으므로 다루기 쉽다. Akka등이 있다.

3.3.3. Dataflow model

큰 데이터 입력에 대한 명령을 미리 정의하여 자동으로 분산 처리하는 방법이다. 개발자는 데이터 입력과 데이터에 대한 연산만 신경 쓰면되고 나머지는 분산시스템이 알아서 처리하는 방식이다. 연산은 주로 데이터 변형을 의미한다. MapReduce 프레임워크 등이 있다.

3.4. 각 모델 별 특징

3.4.1. 구현 수준

각 언어/시스템은 어떤 방식으로 구현되었는지에 따라 크게 3가지로 나뉠 수 있다. 첫번째는 시스템 또는 OS 이며 Mirage가 있다. 두번째는 언어이며 Erlang이 있다. 세번째는 라이브러리이며 MapReduce가 있다.

3.4.2. 로직이나 상태의 처리 단위(Granularity, Grain Size)

각 분산 프로그래밍 언어는 다루는 객체의 기본 단위에 따라 구분될 수 있다. 예를들어 Actor 시스템은 여러개의 Actor가 명령을 처리하는 식으로 구현되며 fine grain(고운 알갱이를 단위로 사용)로 구분되며, Dataflow는 큰 데이터셋과 큰 변형 연산을 입력으로 받으므로 coarse grain(굵은 알갱이 단위로 사용)로 구분된다.

Actor가 fine grain으로 구분되는 이유는 각 워커(액터)가 로직이나 상태를 정의하고 있기 때문이다. 즉 Actor는 로직을 모듈화할 수 있다는 장점을 갖는다. 단점으로는 로직을 개발자가 모듈화하고 배포하므로 하므로 스케일링, 스케쥴링, 부하분산, 프로세스 위치 및 배포, 실패 처리등에 책임을 져야한다.

dataflow가 coarse grain으로 구분되는 이유는 하나의 명령어 세트에 여러 워커를 할당하기 때문이다. 전체 로직은 하나로 묶여 있다. 워커들은 데이터와 로직을 받아서 실행한다.

각 방식의 처리 단위를 측정하는 이유는 크기에 따라 여러가지 서로 다른 문제가 발생할 수 있기 때문이다.

처리 수준(단위) grain size(알갱이 크기) 병렬성 관련 문제
명렁어 수준 fine highest 동기화, 스케줄링
루틴 수준 Medium moderate
프로그램 수준 Coarse least 불균형

3.4.3. 추상화 수준

언어별로 요소에 대한 추상화 수준이 다르다.

  • 부분 실패: OS수준 또는 개발자가 개발한 커스텀 핸들러로 처리할 수 있음
  • 리소스 할당: 시스템이 자동으로 특정 노드에 작업을 할당할 수 있으며 개발자가 주소를 지정할 수도 있음
  • 스케일링: 시스템이 알아서 스케일링을 할 수도 있고 개발자가 반드시 스케일링 문제를 다뤄야하는 경우도 있음 (Dataflow vs Actor/DSM)