LINE Engineering
Blog

함수형 프로그래밍 언어에 대한 고찰

김부성, 이재호 2018.01.11

김부성: LINE에서 Game Promotion Platform을 개발하고 있습니다. 프로그래밍 언어에 대해 관심이 많으며 최근에 Kotlin을 배우기 시작했습니다. 이재호: LINE에서 Game Tech PM업무를 하고 있습니다. 최근에는 Javascript에 관심을 가지고 있습니다.

안녕하세요, LINE에서 게임 플랫폼을 개발하는 주니어 개발자 김부성, 이재호입니다. 저희는 LINE Game Cloud가 함수형 프로그래밍 언어 중 하나인 Clojure로 구현되어 있는 것을 보고 함수형 프로그래밍 언어에 관심을 가지게 되었습니다. 이 글은 LINE Game Cloud의 사례를 통해 함수형 프로그래밍 언어의 몇 가지 특징을 소개합니다.

LINE Game Cloud와 함수형 프로그래밍

LINE은 게임 서비스를 전세계에 안정적으로 제공하기 위해 LINE Game Cloud라는 클라우드 기반의 게임 서버 플랫폼을 구축하여 운영하고 있습니다. LINE Game Cloud는 서비스의 글로벌화와 배포 과정의 자동화를 목표로 시작된 프로젝트입니다. 현재 LINE Game Cloud는 세계 각지에서 해외 사용자들을 대상으로 게임 서비스를 제공하고 있으며, 서버 자동 발급과 L4/L7 라우팅, DNS, Auto Scaling을 지원합니다. 프로젝트에 대한 세부 내용은 다음의 링크를 통해 확인하실 수 있습니다.

LINE Game Cloud의 구조

LINE Game Cloud는 아래의 그림과 같이 구성되어 있습니다. 도커 컨테이너(Docker Container)의 provision을 담당하고 여러 설정 정보를 생성하는 Orchestration Engine과 하나의 물리 장비 위에서 동작하며 Orchestration Engine이 생성한 정보를 바탕으로 도커 컨테이너들을 제어하는 Provisioning Agent, 각 도커 컨테이너상에서 서비스 인스턴스를 관리하는 Node Agent로 구성되어 있습니다. 세 모듈 모두 Clojure로 구현되어 있습니다.

LINE Game Cloud가 함수형 프로그래밍을 선택한 이유

처음 LINE Game Cloud 개발에 착수했을 때 가용 인원은 고작 두 명 뿐이었습니다. 개발 인력과 시간이 충분하지 않은 상황에서 전세계적인 서비스를 제공하기 위해 저희는 기존과는 다른 새로운 접근 방법이 필요하다고 생각했습니다. 먼저, 프로토타입을 만들고 검증하는 일련의 개발 과정을 빠르게 진행하기 위하여 최대한 간결하게 프로그램을 작성해야 했습니다. 또한, 전세계 규모의 방대한 데이터를 효율적으로 처리하기 위하여 동시성을 잘 지원하는 프로그램을 작성해야 했습니다. 이에 더하여 안정적인 런타임 환경이 필요했고, 기존에 작성된 모듈들을 최대한 재사용하고 싶었습니다.

저희는 함수형 프로그래밍 언어가 우리의 요구사항에 잘 부합한다고 생각하였습니다. 함수형 프로그래밍 언어를 사용하면 코드를 간결하게 작성할 수 있어 개발 시간을 단축할 수 있고, 함수형 프로그래밍 언어가 부작용(Side Effect)를 허용하지 않는 순수 함수(Pure Function)를 지향하여 동시에 여러 스레드에서 문제 없이 동작하는 프로그램을 쉽게 작성할 수 있기 때문입니다. 저희의 선택은 Clojure였습니다.

함수형 프로그래밍 언어의 특징

LINE Game Cloud를 통해 바라본 함수형 프로그래밍 언어의 몇 가지 특성에 대해 알아보겠습니다.

불변성(Immutability)

함수형 프로그래밍 언어는 불변성을 지향하는 프로그래밍 언어 패러다임이라고 말할 수 있습니다. 즉, 변경 가능한 상태를 최대한 제거하려고 노력한 프로그래밍 언어입니다. 순수 함수를 지향하는 프로그래밍 언어라고 설명하기도 합니다. 순수 함수는 내부 상태를 갖지 않아, 같은 입력에 대해서는 항상 같은 출력이 보장되는 함수입니다. 다르게 표현하면, 부작용(side effect)이 없는 함수이기도 합니다. 수학에서의 삼각함수가 순수 함수의 한 예입니다. 함수 내부에 상태가 존재하지 않으며, 함수의 출력 값은 항상 함수의 입력 값의 영향만 받기 때문입니다. 함수형 프로그래밍 언어가 불변성을 추구함에 따라 여러 가지 장점을 제공합니다. 그 중 몇 가지를 다음과 같이 소개합니다.

프로그램의 검증이 쉽다

불변성은 프로그램의 검증을 수월하게 합니다. 프로그램을 구성하는 모듈들이 오로지 입력 값의 영향만 받기 때문에 테스트 코드를 작성하기 쉽고, 프로그래머가 예측하지 못하는 시점에 변경될 수 있는 내부 상태가 없기 때문에 프로그램이 예측 가능해지기 때문입니다.

최적화가 가능하다

불변성은 다양한 최적화를 가능하게 합니다. 이전에 계산한 함수의 값을 캐싱(caching)해 두었다가 필요할 때 다시 사용하는 메모이제이션(memoization)은 함수의 불변성이 보장되지 않으면 불가능합니다. 호출할 때마다 결과 값이 달라지는 함수는 캐싱을 하는 것이 의미가 없기 때문입니다. 불변성은 JVM이나 CLR 같은 런타임 환경이 코드의 순서를 임의로 재배치할 수 있는 근거가 됩니다. 결과가 함수의 호출 순서와는 관계 없이 오로지 함수의 입력 값에만 의존하기 때문에, 필요한 경우 런타임이 자발적으로 코드의 실행 순서를 변경하여 성능을 향상시킬 수 있는 것입니다.

LINE Game Cloud에서는 다음과 같이 메모이제이션을 활용합니다. make-auth-config-라는 함수는 username과 password, server-address라는 세 인수를 입력 받아 AuthConfig라는 클래스의 인스턴스를 반환합니다. 이 함수는 순수 함수입니다. 내부 상태를 가지고 있지 않으며, 함수의 결과가 오로지 함수의 입력 값에만 영향을 받기 때문입니다. 그렇기 때문에 이 함수는 메모이제이션이 가능합니다(마지막 줄). 이에 따라 함수가 1회라도 호출된 이후 동일한 값을 가진 인수 조합(username, password, server-address)이 입력되면 함수의 몸체를 실행하지 않고 이전에 저장해놨던 결과를 반환할 것입니다.

(defn make-auth-config-
  [username password server-address]
  (-> (AuthConfig/builder)
      (.username username)
      (.password password)
      (.serverAddress server-address)
      (.build)))

(def make-auth-config (memoize make-auth-config-))

동시성 프로그램을 작성하기 쉽다

멀티프로세서 환경에서 동작하는 동시성 프로그램을 작성할 때 함수형 프로그래밍 언어가 유용합니다. 동시성 프로그램을 작성하기 힘든 이유는 여러 스레드(Thread)들이 프로그램 상태를 공유하기 때문이었습니다. 함수형 프로그래밍 언어에서는 변경 가능한 상태를 원천적으로 배제하기 때문에 프로그래머는 잠금(Lock)과 동기화(Synchronize)와 같은 골치 아픈 스레드 관련 문제에서 벗어나 핵심 로직 구현에 집중할 수 있습니다.

First-class, higher-order functions

함수형 프로그래밍에서 함수는 일등 시민(first-class citizen)입니다. 일등 시민으로서의 함수란 변수에 할당할 수 있고, 다른 함수의 인자로 전달할 수 있으며, 다른 함수의 결과 값으로 반환될 수 있는 함수를 의미합니다. 함수를 하나의 값처럼 다룰 수 있기 때문에 객체지향 패러다임에서 클래스를 재사용하는 것처럼 함수를 재사용할 수 있고, 핵심 코드를 boilerplate 없이 간단하게 표현할 수 있습니다. 최근 많은 프로그래밍 언어가 람다(익명) 함수를 지원하게 된 배경도 boilerplate 없는 간결한 함수를 전달하기 위함이라고 생각합니다. 일등 시민으로서의 함수는 고차 함수(Higher-order function)의 표현을 가능하게 합니다. 고차 함수란 인수로 전달된 함수를 이용하여 만든 새로운 함수를 의미합니다. 고차 함수는 부분 적용(partial application)이나 커링(currying)을 가능하게 만들어 프로그래머가 프로그램을 간결하게 작성할 수 있도록 도와줍니다. LINE Game Cloud에서 일등 시민으로서의 함수와 고차 함수가 어떻게 활용되는지 살펴보겠습니다.

(defn listing-all-containers [docker]
  (let [lists (.listContainers docker (into-array [(com.spotify.docker.client.DockerClient$ListContainersParam/allContainers)]))]
    (map (fn [list]
           [:id (.id list)
            :names (.names list)
            :image (.image list)
            :imageId (.imageId list)
            :command (.command list)
            :created (.created list)
            :status (.status list)
            :ports (.ports list)
            :labels (.labels list)
            :sizeRw (.sizeRw list)
            :sizeRootFs (.sizeRootFs list)]) lists)))

위의 listing-all-containers 함수는 모든 도커 컨테이너(Docker Container)들의 목록을 나열할 때 사용되는 함수입니다. 함수의 흐름은 인자로 받은 docker라는 인스턴스(instance)의 메소드(method)인 listContainers를 호출하여 컨테이너 리스트를 얻고, 이 리스트의 각 원소에 람다 함수를 적용하여 새로운 컬렉션(collection)를 만드는 것입니다. 선언부를 제외하고 위의 함수를 단순하게 표현하면 다음과 같이 표현할 수 있습니다.

(map
    (fn ...)
    lists)

map 함수는 두 개의 인자를 입력 받습니다. 첫 번째 인자는 람다 함수로 lists 원소 각각에 적용할 코드 블록이고, 두 번째 인자는 람다 함수를 적용할 컬렉션(collection)입니다. 이처럼 함수형 프로그래밍 언어에서는 함수를 다른 함수의 인자로 전달할 수 있습니다. 함수가 first-class citizen이기 때문입니다. 또한, 위 예에서 map 함수는 고차 함수의 한 예이기도 합니다. map 함수와 람다 함수를 조합하여 새로운 함수를 만든 것입니다.

Lazy evaluation

함수형 프로그래밍 언어는 지연 연산(lazy evaluation)을 지원합니다. 지연 연산이란 어떤 값이 실제로 쓰이기 전까지 그 값의 계산을 최대한 미루는 것입니다. 값을 미리 계산하여 저장하지 않기 때문에 공간을 절약할 수 있고, 값이 꼭 필요할 때만 계산하기 때문에 프로그램의 성능에도 긍정적인 영향을 줍니다. 지연 연산을 이용하면 무한수열을 표현할 수 있는 장점도 있습니다. 수열의 모든 값을 메모리에 저장하는 것이 아니라 수열의 표현을 선언하고 값이 필요할 때 계산하여 원하는 값을 생성하기 때문입니다. 지연 연산은 주로 메모이제이션과 함께 사용됩니다. 값이 필요할 때 계산을 수행한 후, 다음에 해당 값이 필요할 때는 계산하지 않고 캐싱해 놓았던 값을 재사용하는 것입니다. 지연 연산의 장점을 잘 표현하는 예제로는 피보나치 수열(Fibonacci sequence)이 대표적입니다.

아래 코드에서 fib라는 이름의 피보나치 수열을 생성하는 함수를 정의했습니다. 이 함수는 개수가 정해지지 않은 무한의 피보나치 수열을 생성하도록 구현된 것으로, 아직 값이 필요하지 않기 때문에 아래와 같이 선언해도 실제로 아무런 연산을 수행하지 않습니다.

(defn fib []
    (map first
        (iterate
            (fn [[a b]] [b (+ a b)])
            [1 1])))

아래와 같이 필요한 개수를 인자로 넘겨 함수를 호출하면 계산을 시작합니다.

(take 10 (fib))
;Output => (1 1 2 3 5 8 13 21 34 55)

마치며

지금까지 LINE Game Cloud를 통해 함수형 프로그래밍 언어의 몇 가지 특징에 대하여 알아보았습니다. (LINE Game Cloud가 함수형 프로그래밍 언어를 선택하게 된 이유에 대해 공유해주신 최영호님께 감사드립니다.) 함수형 프로그래밍 언어로 만든 프로그램은 생산성과 유지보수성, 동시성 측면에서 여러 장점을 가지고 있습니다. 게임 분야에서도 함수형 프로그래밍 언어를 활용하는 다양한 시도가 이루어지고 있으며 성공 사례가 등장하고 있습니다. LINE도 이러한 추세에 발맞추어 게임 서버 개발에 함수형 프로그래밍 언어를 적극 활용하는 방안을 고민하고 있으며 앞으로 좋은 성과가 나오기를 기대합니다. 읽어주셔서 감사합니다.

Functional Programming LINE Game Cloud Clojure Server

김부성, 이재호 2018.01.11

김부성: LINE에서 Game Promotion Platform을 개발하고 있습니다. 프로그래밍 언어에 대해 관심이 많으며 최근에 Kotlin을 배우기 시작했습니다. 이재호: LINE에서 Game Tech PM업무를 하고 있습니다. 최근에는 Javascript에 관심을 가지고 있습니다.

Add this entry to Hatena bookmark

리스트로 돌아가기