brunch

You can make anything
by writing

- C.S.Lewis -

by 매직테이블 Aug 02. 2018

구글의 프로그래밍 언어, Golang이 매력적인 이유

Golang을 백엔드에 적용할 때 나타나는 강점들


안녕하세요, 다채널 광고 분석 솔루션 매직테이블의 제품개발팀입니다. 매직테이블의 주요 개발 백엔드 언어로 Golang을 사용한지 벌써 1년이 되었네요. 기존 제품은 python으로 백엔드를 구성했기 때문에, 작년에 Golang을 매직테이블의 메인 개발언어로 선택할 당시 걱정이 앞섰습니다. 그러나 1년 정도 사용해 오면서, 단점 보다는 장점이 더 많은 언어라는 확신을 가지게 되었네요. 그래서 오늘은 Golang 언어를 백엔드에 적용하면서, 저희 제품개발팀이 현장에서 경험한 장점을 설명드리고자 합니다.


* 예제가 실행되는 환경은 별도의 언급이 없는 한 mac OS의 터미널이 기준입니다.

* Golang에서의 패키지는 python의 모듈, Java의 패키지와 비슷한 개념입니다. import하여 사용할 수 있습니다.





1. 실행 파일 생성


Golang으로 OS별 실행 파일을 생성하는 방법은 간단합니다. 모든 개발 언어의 단골 예제인 Hello, world! 를 터미널에 보여주는 프로그램을 만들고 싶다고 가정해 봅시다. 다음과 같이 main.go 파일을 생성합니다.

그리고 터미널에서 다음과 같은 명령어를 입력합니다.

그러면 같은 폴더에 main 이라는 이름의 실행 파일이 생성됩니다. 이 실행 파일은 main() 함수의 내용을 실행합니다. 해당 파일에 실행 권한을 부여한 후, 실행해보죠.

정말 쉽네요. 만약 mac OS에서 linux용 실행 파일을 빌드하는 것도 환경 변수만 지정하면 가능합니다. 


Golang 문서에서는 Free BSD, Linux, Mac OS, Window를 지원한다고 되어 있습니다. 주요 OS는 모두 지원하네요!


실행 파일 생성이 쉬운 점은 OS 상에 데몬으로 띄우기가 간편하다는 장점으로 이어집니다. 실무에서 자주 쓰이는 Linux (Ubuntu, Centos)의 경우를 예를 드면, 별도의 프로그램 없이 service 파일을 작성해, systemctl로 직접 관리할 수 있습니다. Golang은 별도의 라이브러리 없이 내장 라이브러리로 웹 어플리케이션의 생성이 가능하기 때문에 , 명령어 한줄로 웹 어플리케이션을 실행파일로 빌드 후, 리눅스의 시스템 데몬으로 띄어주기만 하면 됩니다. 


service 파일은 다음과 같이 작성하면 됩니다.

파일 이름이 webapp.service라면 /etc/systemd/system 폴더에 있다면, 다음과 같은 명령어로 바로 웹 어플리케이션을 시작할 수 있습니다.

Mac OS 같은 경우도 launchctl을 이용해 실행 파일을 직접 데몬으로 띄어 사용 및 관리할 수 있습니다. 이처럼 실행 파일 생성이 쉬우면, 서버에 간단하게 배포가 가능합니다. 특히 마이크로아키텍쳐를 적용하고 있는 서비스의 경우, 서버의 확장이나 배포가 쉽기 때문에 장점이 극대화됩니다. 또한 작은 앱을 만들어서 바로 실험을 해보고 싶을 때, 별도의 설정 없이 빠르게 개발 통합 환경에 올려서 테스트해볼 수 있다는 장점이 있습니다.


 


2. 에러 처리


go의 에러 처리는 직관적입니다. python의 경우와 비교를 해봅시다. 정수 값이 2 이하면 Exception을 내보내는 함수를 만들고, 그 함수를 사용하는 python 예제를 만들어 보겠습니다.

동일한 코드를 go로 구현해 보겠습니다.

터미널에 나오는 결과는 동일합니다.

가장 큰 차이는 go 같은 경우, error를 변수로 돌려준다는 점입니다. 에러가 발생했는지 확인해 보고 싶다면 error가 nil인지만 확인하면 됩니다. 일반적으로 고급 언어에서 사용하는 try, catch 방식이 아닌 인자로 error를 넘기는 방식을 사용하면 복잡한 exception 처리도 일련의 코드로 할 수 있습니다




3. 태그 사용


Go 언어는 정적 언어 입니다. 변수별로 타입을 지정해야 합니다. 정적 언어는 빌드(compile)할 때, 정적 분석을 통해 문법 오류를 잡아내는 장점이 있지만 유연하지 않다는 단점이 있습니다. 이러한 단점을 보완하기 위해 Go에서는 동적으로 변수를 생성하거나 관리할 수 있는 interface, reflect, tag를 제공합니다. 그 중 Go에만 있는 특수한 기능인 tag를 설명해 드릴까 합니다.

Go는 C언어와 비슷하게 클래스 개념으로 “type 타입명 struct”를 사용하는데, 위 코드와 같이 Person struct를 type으로 정의했다고 해봅시다. Name, Age는 타입으로부터 생성되는 인스턴스의 프로퍼티라 보시면 됩니다. Name, Age 정의 뒤에 json:”name” 형식으로 들어가 있는 부분이 tag 입니다. 이 json tag는 Go 의 내장 라이브러리인 “encoding/json” 라이브러리를 사용하기 위한 태그 입니다. main 함수에 있는 json.Unmarshal은 json 정보를 담고 있는 byte array를 person 객체에 넣어주는 역할을 합니다. json의 key가 type에 태그로 정의가 되어 있는 key가 일치할 경우, person 객체에 값을 넣어줍니다.


실행 결과를 보면 객체에 값이 잘 들어간걸 알 수 있습니다.

tag를 해석하는 패키지(라이브러리)나 함수를 만들려면, reflect라는 녀석을 알아야 합니다. 여기서 reflect까지 다루게 되면 내용이 길어지므로 생략하겠습니다. 기회가 있으면 다음에 다루어 보겠지만, 우선 궁금하시다면 해외 블로그를 검색하면 예제가 많으니 참고하시면 됩니다. 대신 tag를 사용한 패키지(라이브러리)를 몇 개 소개해 드릴게요. 


gorm : ORM 라이브러리. ORM에서 DB column 명과 설정을 매칭 시키는 역할에 태그를 사용
validator: struct의 각 필드에 할당된 값을 validation하는 패키지. struct의 필드별 validation 기준에 tag를 사용한다.




4. goroutine


goroutine은 concurrency를 위해 go runtime에서 직접 관리하는 lightweight thread 입니다. thread라고 해서 OS thread를 의미하는건 아니고, go runtime의 virtual space 상에 존재하는 thread 입니다. goroutine을 하나 생성하는데 초기에 필요한 메모리가 2KB 밖에 안되기 때문에, 기존 OS thread를 생성하는데 필요한 1MB와 비교했을 때, 비교가 안될 만큼 작습니다. (1000개를 동시에 생성한다고 생각하고 계산해보세요. 각각 2MB와 1GB의 메모리를 차지하게 됩니다. 차이가 크죠?). Goroutine은 2KB로 현재 goroutine 유지가 힘들다고 판단되면, 메모리의 다른 영역에 X2 만큼의 영역을 확보해 복사하는 방식으로 메모리 관리를 해줍니다.


간단한 goroutine을 사용하는 프로그램을 만들어 봅시다.

“//” 가 들어가 부분은 주석이므로 우선 무시하셔도 됩니다. go hello() 부분은 새로운 goroutine을 생성해, hello() 함수를 실행하라는 뜻입니다. world()는 main goroutine에서 실행되고 있구요. 위 코드를 실행하면, hello, world가 전부 찍힐 것 같지만, world 만이 콘솔에 찍히게 됩니다. 이는 main() 함수 내부는 main goroutine이 실행되는 영역인데, 이 부분이 종료되면, main goroutine에서 생성된 goroutine은 무시가 됩니다. 그래서 world만 나오게 되는 것입니다.


그렇다면 주석을 제거해 볼까요? 그러면 1초 정도 main goroutine이 sleep을 하게 되는데, 이는 go hello()가 실행되기 충분한 시간인것 같네요. 역시 제거하고 실행해보면 hello와 world 모두 console에 찍히게 됩니다.


사실 좀 찝찝하지 않나요? 사실 이렇게 감으로 시간을 추정하여, 병렬 코드가 돌아가도록 기다리는 방식은 초보적인 방법입니다. 실행 시간이 긴 goroutine의 경우 이렇게 코드를 작성하면, 실행되는 중간에 main goroutine이 종료되어 빛을 보지도 못할 수도 있습니다. goroutine을 사용하면서도, 확실히 끝날 때까지 기다리는 방법이 없을까요? 이렇게 하려면, 새로 생성된  goroutine에서 main goroutine으로 “야, 나 끝났어” 하고 신호를 보내는 방법이 필요합니다. 이 역할을 하는게 channel 입니다. 우선 channel을 사용한 코드를 보시죠.


위 코드를 보면 task라는 channel 변수를 만들어 사용합니다. channel 은 goroutine 사이에 공유되는 변수이고, 들어온 순서대로 밀어내는(FIFO) 큐(queue) 버퍼입니다. 


make(chan int)는 int 가 담기는 channel 변수를 생성하는 코드입니다. hello() 함수를 실행하기 위해 goroutine을 생성하고, task를 함수의 인자로 보냅니다. 그리고 main 함수는 계속 진행되겠죠. “<-task”를 다음으로 실행하게 되는데, 이는 task 채널에 값이 들어올 때 까지 기다리고, 들어오면 그 값을 내보내라는 의미를 가집니다. 즉, hello 함수 내에 “Hello”가 인쇄되고, task<-0 이 실행되기 전까지 main goroutine는 “<-task”에서 기다리게 됩니다. 이처럼 channel을 사용하면, main goroutine과 goroutine 사이에 통신을 쉽게 할 수 있습니다. 또한 channel 다음과 코드와 같이 생성하면, 2개의 buffer 공간이 있는 channel 변수도 만들 수 있습니다.


즉, 2개의 int는 미리 담아 놓을 수 있게 됩니다. Buffer 공간이 없는 경우, channel 변수에 하나의 값을 받으면, 그 channel 변수는 그 값이 빠져나갈 때까지, 추가로 값을 못받습니다. 이러한 큐의 버퍼 특성은 병렬 작업에 대해, 최대 생성되는 goroutine 개수를 제한하는 카운팅 세마포어(counting semaphore)에 사용될 수 있습니다.


이처럼 go 언어는 concurrency 처리 코드를 쉽고 유연하게 작성할 수 있습니다. goroutine의 성능은 당연한 이점이고요!


지금까지 1년 동안 go 언어를 사용하면서 느낀 장점을 적어보았습니다. 저희도 1년 밖에 언어를 사용해보지 않아 아직 전문가라 할 수 없지만, Go라는 언어를 사용해보고 싶거나 관심이 있는 분에게는 충분한 동기를 주지 않았나 하는 생각이 드네요. 마지막으로 동기를 더 부여해드리기 위해 Golang을 사용하는 서비스나 오픈소스 라이브러리를 소개해 드리는 것으로 마치겠습니다.


Docker: 요즘은 모르시는 분이 없겠죠? :-)

트위치(Twitch): 엄청난 트래픽과 로드 덕분에 Golang의 발전에 기여한 서비스 입니다. 게임 스트리밍 서비스를 제공합니다. 최근에 Twirp라는 Golang 기반 RPC 프레임워크도 공개했습니다.  

Kubernates디플로이 자동화, 스케일링, 컨테이너화된 애플리케이션의 관리를 위한 오픈 소스 시스템으로서 원래 구글에 의해 설계되었고 현재 리눅스 재단에 의해 관리된다고 하네요.

Prometheus: pull 방식의 오픈소스 모니터링 시스템. 

Google: golang은 구글이 만든 언어 입니다.  


그 외에, Uber, dropbox, twitter등 저명한 서비스도 부분적으로 golang을 사용하고 있습니다.


다채널 광고 분석 솔루션, 매직테이블

매거진의 이전글 알고 계신가요? 자바스크립트 핵심 기능, 클로저!

매거진 선택

키워드 선택 0 / 3 0

댓글여부

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

카카오계정으로 간편하게 가입하고
좋은 글과 작가를 만나보세요

카카오계정으로 시작하기
다른 SNS로 가입하셨나요?