Seoul Elixir Meetup 8월 17일 - Why Processes Trump Objects?

Posted on Thursday, 18 Aug 2016 Tags: elixir elixir-meetup talk 

이 글은 어제 처음으로 열린 Seoul Elixir Meetup에서 주최자 Kevin Rockwood가 발표한 내용을 기억나는 대로 정리한 것입니다. 말 그대로 제가 기억하는 대로 적은 것이기 때문에 글의 흐름이 그다지 매끄럽지 못하고 틀린 내용이 있을 수 있습니다. 이러한 사항에 대해 지적하실 내용이 있으시면 연락해 주시기 바랍니다.

발표에 사용된 프레젠테이션은 여기서 보실 수 있습니다.


원조(?) 객체 지향이란

객체 지향이란 무엇일까요? 아마 대부분의 사람들이 객체 지향이라고 하면 아래와 같은 것들을 가장 먼저 떠올리지 않을까 합니다.

  • 클래스와 인스턴스
  • 메소드 호출
  • 상속, 상속, 상속

하지만 옛날 사람들이 생각해낸 객체 지향은 우리가 지금 알고있는 것과는 꽤 거리가 있습니다. 1970년대 Xerox의 연구소에서 근무하던 Alan Kay는 기존의 절차지향적 프로그래밍 패러다임에서 벗어나 더 나은 방식으로 프로그램을 개발하고자 했는데요, 분자생물학 학사학위를 가지고 있던 그는 우리 몸을 구성하고 있는 세포들의 특징에 착안하여 다음과 같은 주장을 하였습니다.

프로그램은 수많은 객체들로 이루어져 있으며,

  • 모든 객체는 완전히 독립적이어야 한다.
  • 객체 간에는 메시지 전달을 통해서만 통신이 가능하다.
  • 모든 객체는 장애로부터 복원될 수 있어야 한다.

이는 현재 우리가 사용하고 있는 인터넷과 비슷하다고 볼 수 있습니다. 네트워크에 연결된 모든 컴퓨터(세포)들은 독립적이며, 메시지 전달(HTTP 등)을 통해서만 서로 통신이 가능하고, 하나의 노드에 장애가 생긴다고 해서 전체 네트워크가 마비되지 않는다는 점에서 말이지요.

Erlang과 OTP의 탄생

이제 1986년 이야기로 한 번 넘어가 봅시다. 휴대전화로 유명한 Ericsson 사에서는 그 당시 새로운 전화 자동 교환 시스템을 구축하려고 했는데, 이 시스템은 아래 조건을 만족해야 했습니다.

  • 분산된 시스템
  • 장애 복원력
  • 실시간 처리
  • 높은 가용성

이러한 조건을 충족시키기 위해서 Ericsson에서 만들어낸 것이 바로 Erlang 프로그래밍 언어와 OTP 프레임워크입니다. Erlang은 동적이며, BEAM 바이트코드로 컴파일되고, 값의 불변성이 있는(immutable) 함수형 프로그래밍 언어이며, 모든 Erlang 프로그램은 여러 “프로세스”로 구성될 수 있습니다. 여기서 프로세스란 OS에서 관리하는 일반적인 프로세스가 아니며, 각각의 프로세스는 매우 가볍기 때문에 한 컴퓨터 내에서 수천 개 이상의 프로세스가 동시에 실행될 수 있습니다.

OTP(Open Telecom Platform)는 Erlang 프로세스의 생명 주기를 관리하는 라이브러리로 구성되어 있으며, Erlang 프로세스를 이용해 액터 모델을 구현한 것입니다. 핫 코드 스와핑을 통해 시스템을 오프라인 상태로 내리지 않고도 실시간으로 변경된 코드를 반영할 수 있다는 특징 등이 있습니다.

Elixir로 OTP 기반 서비스 만들기

시간은 흘러 2011년, José Valim이라는 사람은 이 Erlang과 OTP에 매력을 느끼고 있었습니다. 하지만 Erlang 특유의 불친절하고 어려운 문법과 전화 교환 시스템 구축이라는 목적 위주로 구성된 기능들이 성에 안 찼던 그는 Erlang을 기반으로 새로운 프로그래밍 언어를 만들었는데, 이것이 바로 Elixir(엘릭서)입니다. Elixir는 Erlang 기반으로 만들어졌고, Erlang VM 위에서 실행되기 때문에 Erlang과 OTP가 가지고 있는 장점들과 Erlang용으로 개발된 방대한 양의 기존 라이브러리를 그대로 사용할 수 있고, 상당수의 문법적 요소를 Ruby 등의 프로그래밍 언어로부터 차용하였기 때문에 친숙하고 배우기 편한 문법을 제공합니다.

따라서 Elixir에서도 Erlang의 프로세스를 그대로 이용할 수 있습니다. 각각의 프로세스는 다음과 같은 특징을 가지고 있습니다.

  1. 모든 프로세스는 메시지를 이용하여 통신합니다.

    "Processes 1" sends a message to "Process 4"

  2. 각각의 프로세스는 상태(State)를 지니고 있습니다.

    iex> {:ok, pid} = MyProcess.start()
    # pid의 상태는 []
    
    iex> MyProcess.push(pid, "Hello, world!")
    # pid의 상태는 ["Hello, world!"]

이번 meetup의 발표자 Kevin은 OTP를 이용한 서비스의 예로 간단한 웹 크롤러 애플리케이션에 대해 설명하고 라이브 시연을 보여줬습니다. 해당 예제 프로젝트는 GitHub에서 볼 수 있습니다. 이 웹 크롤러는 다음과 같이 다섯 개의 구성 요소로 이루어져 있습니다.

A web server, a socket manager, a crawler, a crawler registry and a connection pool

여기서 각각의 요소는 모두 개별적인 프로세스입니다.

이 프로세스들을 두 개의 앱으로 묶어보겠습니다. 웹 서버와 소켓 매니저를 App 1로, 크롤링과 관련된 프로세스들은 App 2로 묶습니다. 여기서 App 1은 Phoenix 프레임워크를 기반으로 구현되었습니다. 참고로 각각의 앱은 한 컴퓨터에서 같이 실행될 수도 있고, 서로 다른 컴퓨터에서 실행될 수도 있습니다. 이러한 특징 덕분에 필요에 따라 유연하고 편리하게 서비스 규모를 확장할 수 있습니다.

마지막으로 이 두 앱을 다시 Umbrella로 묶습니다. 이렇게 하면 하나의 큰 프로젝트 내에서 여러 하위 애플리케이션 프로젝트를 효율적으로 관리할 수 있습니다.

이 서비스는 아래와 같이 동작합니다.

사용자의 웹 브라우저와 크롤러 사이에는 Phoenix 채널이 매개체로써 자리잡고 있습니다. 여기서 사용자가 어떤 웹 사이트를 크롤해달라는 요청을 하면 Phoenix 측 프로세스에서는 크롤러 프로세스로 어떤 사이트의 어떤 페이지를 크롤링해달라는 메시지를 보냅니다. 크롤러 프로세스가 메시지를 받으면 이 프로세스는 다시 커넥션 풀 프로세스에 메시지를 보내 현재 남아있는 연결이 있는지를 확인하고, 있으면 해당 연결 리소스를 이용하여 페이지를 크롤링하고, 크롤링된 페이지에 대한 정보를 메시지에 담아 다시 Phoenix 채널로 보냅니다. Phoenix 채널에서 메시지를 수신하면 해당 메시지를 처리하여 사용자의 웹 브라우저에 표시합니다.

추가적으로, OTP에서는 일반적인 프로세스 외에도 하위 프로세스를 관리하는 Supervisor라는 특수한 프로세스를 만들 수도 있습니다. 슈퍼바이저는 하위 프로세스를 감시하면서 어떤 하위 프로세스에 문제가 발생하여 실행이 중단되었을 때 해당 프로세스를 다시 실행시키거나 전체 프로그램의 실행을 중단시키는 등의 처리를 하는 역할을 맡고 있으며, Erlang/OTP 기반의 시스템들이 뛰어난 장애 복원력으로 유명해지게 된 주된 요인입니다.

마치며

다시 한번 물어봅시다. 객체 지향이란 무엇일까요? Alan Kay는 객체 지향 프로그램의 조건이 아래와 같다고 말했습니다.

  • 모든 객체는 완전히 독립적이어야 한다.
  • 객체 간에는 메시지 전달을 통해서만 통신이 가능하다.
  • 모든 객체는 장애로부터 복원될 수 있어야 한다.

그리고 Erlang/OTP와 Elixir에서 제공하는 프로세스는 다음과 같은 특징이 있습니다.

  • 모든 프로세스는 완전히 독립적이어야 한다.
  • 프로세스 간에는 메시지 전달을 통해서만 통신이 가능하다.
  • 모든 프로세스는 장애로부터 복원될 수 있어야 한다.

그렇습니다! 프로세스야 말로 처음부터 의도된 객체지향의 개념에 가장 부합하는 요소였던 것입니다.