brunch

You can make anything
by writing

C.S.Lewis

by 채규병 Aug 02. 2020

데이터과학자가 혼자서 실시간 서비스 구현하기

R로 REST API 구축하여 서비스하기




R로 REST API 구축하여 서비스하기


R 언어는 데이터 분석가에겐 필수적인 언어입니다. 가장 큰 장점은 오픈소스라는 것입니다. 전 세계의 훌륭한 통계학자, 개발자들이 만든 패키지를 무료로 마음껏 가져다가 쓸 수 있습니다. 하지만 단점은 그 결과물을 애플리케이션 화하기 어렵다는 것 입니다. R 스크립트를 PHP나 JAVA 등으로 호출하는 방법도 있습니다만, 이는 다른 언어와 혼용해서 작성해야 하는 부담이 있습니다.  R 언어만을 이용하여 API를 작성하여 웹 응용 프로그램을 개발했던 프로젝트 경험을 공유하고자 합니다.





왜 데이터과학자가 서비스를 구현하는가?

데이터 과학에 있어서 분석 결과물을 공유-적용하는 일은 굉장히 힘듭니다. 누구나 이해하기 쉽게 혹은 고객의 입맛에 맞게 써야한다는 문제는 차치하고, 개발한 알고리즘(모델)을 어떻게 적용해야 할지가 고민입니다. 기존의 애플리케이션에 추가 기능을 넣어야 할지, 단순 리포트로 제공할지, 아니면 분석-연구를 공유하고 끝낼지 고민의 방향은 다양합니다. 


게다가 규모가 작은 프로젝트라면 필요한 기술적 지원을 받기 어렵습니다. 프론트엔드, 백엔드, 데이터 엔지니어 등 다양한 역할을 우리(데이터 과학자)가 해야합니다. 여기에서 '분석만 할게요' 혹은 '알고리즘(모델)만 짤게요' 하고 손놓고 있을 수 없습니다. 왜냐하면 그 영세한 프로젝트의 대부분의 단가는 우리를 투입하는 데에 쓰였을 테니까요... 




R을 가지고 실시간 서비스를 구현할 수 있을까?

구현할 수 있습니다. 


실시간이라는 말을 좋아하지는 않습니다만(실시간이란 없다고 생각하기에...), 여기서는 사용자가 요청할 때마다 전달한 파라미터로 R 알고리즘이 돌아간다라고 정의하겠습니다. 저의 경우에는 원하는 날짜를 던지면 해당 기간의 데이터를 DW에 질의해서 가져옵니다. 그 데이터를 기반으로 추천 알고리즘이 돌고, 결과를 JSON 방식으로 리포트 서버에 전달합니다.


우선 R의 plumber 패키지를 불러옵니다.

service.R는 웹 어플리케이션 서버(WAS)에 대한 정보가 있습니다. 실질적인 알고리즘은 myfile.R에 구현이 되어 있습니다.



파일 구조


서비스에 대한 스크립트와 알고리즘 스크립트를 나누면서 로직 관리가 쉬워진다는 장점이 있습니다. 다만, myfile.R을 수정하면 자동으로 서비스로 배포가 되지 않는 단점이 있습니다. 수정할 때마다 서비스를 내렸다가 올려야 하는 부담이 있습니다.



# service.R
setwd('./pgm/');getwd()
libPath <- c("/home", .libPaths())
library("plumber", lib.loc= libPath)
r <- plumb("myfile.R")  # Where 'myfile.R' is the location of the file shown above
r$run(host="0.0.0.0", port=8000)


# myfile.R
#* @get /mean
normalMean <- function(samples=10){
    data <- rnorm(samples)
    return(mean(data))
}


REST API에서 URI 설계는 마치 주석처럼 보이는 구문을 통해서 합니다. myfile.R에서 보면 #* @get 이라는 구문 뒤에 오는 내용이 uri입니다. 파라미터는 그 아래 함수의 파라미터로 자동으로 설정됩니다. 예를 들어, normalMean 함수를 호출하는 uri는 다음과 같습니다.


http://0.0.0.0/find?samples=1000



HTTPs 문제 발생!

열심히 REST API 서비스를 만들어 놓았더니 SAS 리포트 솔루션이 http 통신은 거부하고 https만 허용하는 문제가 발생했습니다. 그래서 방법을 고심하다가 plumber 서버 앞에 서버를 하나 더 두기로 했습니다. 그것이 NginX입니다. 이런 걸 리버스 프록시(Reverse Proxy)라고 하더군요. 


plumber 패키지만 쓰면 https로 서비스를 할 수 없습니다. 이렇게 단호하게 말할 수 있는 이유는 plumber 패키지 공식 문서에서도 RStudio의 Connect나 디지털오션(DigitalOcean), PM2 등을 이용하라고 나와 있기 때문입니다. 그러나 이 방법은 추가적인 비용이 발생합니다.


http로 서비스 요청이 오면 NginX가 https로 리다이렉트하는 방식입니다. key는 openssl을 이용해서 만들어줍니다. 자세한 방법은 구글링하면 쉽게 알 수 있습니다.


웹 서비스 아키텍쳐


단순히 HTTPS를 쓰기 위해서 NginX를 도입했지만, 공부를 하다보니 아주 좋은 기능이 많았습니다. plumber로만 직접 사용자의 요청을 처리하게 하면 3~4명의 요청에도 서버가 응답을 처리하지 못 합니다. 이는 R이 단일 프로세스이기 때문에 생기는 문제입니다. 이를 해결하기 위해선 도커를 가지고 여러 프로세스를 띄우는 방법이 있다고 합니다만, 추후에 다루겠습니다.


# nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    server {

            listen 80;
            server_name 0.0.0.0;
            return 301 https://$host$request_uri;
    }

    server {
            listen 443 ssl http2;
            server_name 0.0.0.0;                        
            ssl_certificate /etc/nginx/private.crt;
            ssl_certificate_key /etc/nginx/private.key;

            location / {
                     proxy_pass http://0.0.0.0:8000/;
                     proxy_redirect http://:8000/ $schem0.0.0.0e://$host/;
                     proxy_connect_timeout 600;
                     proxy_send_timeout 600;
                     proxy_read_timeout 600;
            }
    }

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  650;
    #gzip  on;
    include /etc/nginx/conf.d/*.conf;
}


물론 NginX를 사용해도 멀티 프로세싱이 되진 않지만, plumber 패키지는 응답에 대한 제한시간을 옵션으로 지정할 수 없습니다. 무슨 말이냐면, 1번 작업요청이 5분이 넘어가고 10분이 넘어가도 2번, 3번 요청은 하염없이 기다리고만 있어야 한다는 뜻입니다. 안그래도 화장실이 한 칸 뿐인데 사용시간 제한도 없으면 난감할 것입니다.


nginx를 통해서 응답시간이 10분을 넘어가면 503 오류를 뱉도록 했습니다. 설정도 아주 간단합니다. proxy_connect_timeout 등 옵션을 nginx.conf 파일에 적어주면 됩니다. 알고리즘 자체가 10분을 넘어가진 않지만, 그 뒤 DB와의 통신에서 시간이 오래 걸릴 때가 많았습니다. DB 서버가 cpu 100%를 치는 경우가 많아서 응답이 감감무소식인 시간대가 있었습니다. 


또한 plumber 서버가 무리하지 않게 요청을 순차적으로 처리하게 해주고, 작업요청을 처리해주는 worker 프로세스를 4개를 띄워서 많은 요청이 한꺼번에 와도 기다리면 응답을 받을 수 있게 해주었습니다.





R로 모든 것을 가능하게 만들다

결과적으로 R 언어만으로 RESTful API를 구현함으로써, 데이터 과학자가 보다 쉽고 유연하게 애플리케이션 설계가 가능해졌습니다. 무엇보다 대부분의 상용 솔루션과 연동을 할 수 있는 방법입니다.  

REST API가 아닌 다른 연동 방식들은 서버 원격 제어를 통해 R 스크립트를 직접 실행한다든지 R 내부에서 HTML를 작성해서 리턴하는 방식입니다. 이는 유연하지 못할 뿐더러, 데이터 과학자가 모형 만들기도 바쁜데, 너무나 시간이 걸리는 작업이 되어 버립니다.


다만 R로 대규모 서비스를 만드는 것은 추천하지 않겠습니다. 해당 자료가 많이 없을 뿐더러 사용자가 많을 때에 처리 속도나 안정성 면에서 검증이 안되었기 때문입니다. 다만 저의 프로젝트에서는 사용자가 월 120명 정도이고 빠른 응답속도를 요구받는 상황도 아니었습니다. 그리고 일 배치로 3개월, 1개월, 1주일에 대한 추천결과도 동시에 서비스했기 때문에 실시간 서비스의 부하가 크지 않았습니다.


공부를 하다보니, 도커를 이용하면 더 안정적이고 편하게 서버 작업을 할 수 있다는 것을 알게 되었습니다. 도커도 결국 NginX를 쓰는 것이긴 하지만, 공부를 해봐야겠다는 생각이 들었습니다. 도커를 공부할 좋은 방법이 있을까요.. 참 백엔드 엔지니어 분들이 대단하다고 느껴집니다.





참고:

https://medium.com/@JB_Pleynet/how-to-do-an-efficient-r-api-81e168562731

https://statkclee.github.io/parallel-r/r-restful-api-test.html

https://www.rplumber.io/

http://journal.hsst.or.kr/DATA/pdf/v9_1_39.pdf


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari