태그 보관물: Apache module

C언어로 API 서버 개발, 생각보다 나쁘지 않아요

지난 이음 API 개선에 관한 포스팅 <아파치 모듈로 개발된 API 서버, 이음 베이론을 소개합니다> 를 기억하시나요? 이 포스팅이 올라온 후 이음 개발팀 블로그에는 독자 여러분들의 뜨거운 관심(?)이 빗발쳤습니다. (그리고 아직까지 이 정도의 뜨거운 관심을 받은 포스트는 아직 나오지 않고 있습니다…) C언어로 API를 개발한다는 이야기에 흥미롭다는 반응도 있었지만 아무래도 걱정어린 시선이 많았습니다.

사실 말하자면 저도 루비, 파이썬을 비롯한 고급 언어들의 팬입니다. 그동안은 PHP나 Java (Spring) 같은 옛날 언어로 개발을 해왔지만, 처음 루비 온 레일즈를 접하고 그 깔끔함에 반해서 아직도 뭔가를 개발할 일이 있으면 최대한 레일즈를 쓰고 있구요. 과거 Spring을 쓰면서 직접 설정해줘야 했던 수많은 잡일들을 떠올리면서 ‘그동안 나는 뭘 한걸까…’ 하는 기분을 느꼈습니다. 그렇게 레일즈와 즐거운 시간을 보내던 중, 담당 프로젝트가 변경되면서 저는 C언어로 API 서버를 개발해야 하는 운명에 처하게 되었습니다.

으아니

이게 무슨 소리요! 내가… 내가 C 개발이라니!

처음에는 너무 막막했습니다. 레일즈를 써보신 분은 알겠지만, 기타 잡다한 부분은 프레임웍이 다 해주기 때문에 로직만을 작성하는 데에 익숙해져 있었거든요. 그런 저에게는 모든 것을 직접 해야만 하는 C언어 개발은 조금 불편하긴 했지만 결론적으로, 써보니까 생각보다 나쁘지 않았습니다. 다른 분들이 생각하시는 것처럼 유지보수 헬게이트도 아니구요. 무엇보다 성능이 모든 것을 용서합니다. 아무튼 자세한 이야기는 차차 해보기로 할게요.

Apache Portable Runtime

C 언어를 서버로 개발한다! 라고 하면 사람들은 포트 여는 것부터 response writing까지 모두 로우 레벨에서 처리해야 할 거라고 생각하지만, 다행히 그 정도까지는 아니었습니다. 이음의 API 서버 개발은 APR (Apache Portable Runtime) 이라는 큰 프레임웍 위에서 돌아가고 있습니다. 엄밀히 말하자면 프레임웍이라기보다는, 웹 서버인 아파치와 이음의 비지니스 로직을 연결해주는 라이브러리라고 할 수 있겠네요. 여튼 APR 덕분에, C 프로그래밍은 생각만큼 괴롭지 않습니다. APR의 홈페이지 메인을 보면 아래와 같이 이야기하고 있습니다.

“The mission of the Apache Portable Runtime (APR) project is to create and maintain software libraries that provide a predictable and consistent interface to underlying platform-specific implementations” – APR 프로젝트의 목적은 플랫폼 의존적인 구현에 대한 예측 가능하고 일관적인 인터페이스를 제공하는 소프트웨어 라이브러리를 만드는 것이다.

즉, 사용하는 하드웨어와 OS에 상관 없이 상위 레벨의 구현에만 신경쓸 수 있도록 만든 라이브러리가 바로 APR이라는 의미입니다. (자바의 “Write once, run everywhere” 의 향기를 느낀 건 저 뿐일까요?) 그래서 APR을 사용하면 네트워크 관련 low level operation (HTTP 헤더를 파싱한다던지, 파라미터를 읽어서 저장한다든지) 에 대해서는 신경쓰지 않아도 됩니다. 프로그래밍에 필요한 기초 자료 구조 (list, hash table) 도 라이브러리에 포함되어 있습니다.

무엇보다 매력적인 것은 APR의 메모리 풀 관련 기능입니다. 이 메모리 풀 라이브러리는 C 언어 개발에서 아주 골치아픈 메모리 할당 / 해제 문제를 해결해줍니다. 구체적으로 이야기하자면, APR을 사용하여 메모리를 할당 (apr_pcalloc 함수를 사용) 받으면, 클라이언트에 response를 보내줄 때 자동으로 풀에 할당된 메모리들을 해제해준다는 것입니다. 멋지지 않나요? 아무튼, APR의 은총 덕분에 상당히 많은 부분들을 라이브러리에 의존하면서 개발할 수 있습니다.

정적 타입의 강점

런타임에 변수의 타입을 결정하는 동적 타이핑 기반의 언어인 루비나 파이썬과는 달리, C는 컴파일 타임에 타입을 체크합니다. 타입이 맞지 않으면 오류를 내면서 컴파일조차 잘 되지 않습니다. 이 부분이 스크립트 언어를 주로 사용하던 프로그래머들에게는 불편할 수 있습니다. 하지만 저는 타입 안전성을 컴파일 타임에 확인할 수 있다는 점은, 프로그램의 안정성에 매우 큰 부분을 차지한다고 생각합니다. (물론 C 언어가 타입 안전성을 완벽히 보장하는 언어인지 묻는다면 물론 아닙니다. 하지만 컴파일 타임에 타입 체크가 불가능한 언어에 비해서는 분명한 강점을 가지고 있다고 생각합니다.) 타입 안전한 언어에서는 컴파일 타임에서 에러가 나지 않으면 로직에 문제가 있지 않은 이상은 잘 작동하죠. 버그는 있을 수 있지만 프로그램이 죽는 일은 없습니다. 또한 성능도 뛰어납니다.

compiling

그만큼 컴파일에 시간이 걸리긴 하죠. (물론 짤방정도는 아님)

Performance, Performance, Performance

APR을 사용하여 개발한 API 서버의 성능은 그야말로 압도적입니다. 기계어로 컴파일 되니까요.

Screenshot at Jan 21 16-36-24

더 이상의 자세한 설명은 생략한다

 

물론, 당연하지만, C 개발이 장점만 있지는 않습니다.

 

 

Reinventing the wheel

회사의 모 님이 저에게 물었습니다. “C 언어로 API 개발하는 거 어떤가요?” 저는 대답했더랬죠. “바퀴를 재발명하고 있는 것 같아요.” 그렇습니다. C 언어에는 그 흔한 ORM조차 없습니다. C 언어 자체가 객체지향적인 특성이 거의 없는 절차 지향적 언어이기 때문에, ORM을 만들어서 쓴다 해도 영 불편하죠. 덕분에 쿼리를 쓰고, 전송하고, 결과를 확인하고, 데이터 구조에 저장하는 등의 일들을 직접 한땀한땀 해야 합니다. 물론 공통되는 부분은 함수로 만들어서 재사용할 수 있지만, 누군가가 만들어 놓은 라이브러리를 가져다 쓰는 것에 비하면 개발 속도가 느려질 수밖에 없습니다. (그만큼 좀 더 시스템 하층 구조에 대해 깊게 알 수 있다는 장점은 있습니다.) 하다 보면 “꼭 이런 걸 일일이 해줘야 하나..”  란 생각이 들 때도 있습니다. 그리고 이건 개개인의 취향이겠지만, 저는 누군가가 만들어 놓은 소스 코드를 제가 다시 만드는 일이 그렇게 재미있지만은 않더라구요.

car and wheel

좀 이런 느낌…

라이브러리 뿐만 아니라 배포나 디버깅, 에러 리포팅, DB조회 등등 전반적인 개발 환경 역시 덜 자동화되어 있기 때문에 만약 자동화시키고 싶다면 직접 제작하는 수밖에 없습니다. 일례로, 레일즈 어플리케이션을 개발할 때는 모델 클래스를 잘 만들어 두면 데이터베이스 조회를 레일즈 콘솔을 통해 거의 다 할 수 있었으나, C 기반 개발을 할 때는 특정 데이터를 조회하거나 업데이트할 때에는 SQL을 직접 작성하는 수밖에 없습니다. 만약 조인을 해야하는 상황이라면 그 번거로움은 배가 되고 쿼리문이 길어질 수록 중간에 실수를 해서 개발 속도가 느려질 가능성은 커집니다. SQL에 익숙하지 않은 개발자라면 더 심하겠죠. 익숙하지 않은 DBMS를 사용한다면 더더욱 그럴테구요.

생산성

“C 언어는 생산성이 안좋다.” 라는 생각을 많이들 가지고 계신 것 같더군요. 제가 생각하기에, 생산성이라는 것은 몇 가지 측면으로 분류할 수 있을 것 같습니다. (물론 이거 말고도 더 있겠죠?)

  1. 같은 길이의 코드로 얼마나 더 많은 일을 할 수 있는 지
  2. 주어진 스펙을 얼마나 빠르게 개발할 수 있는 지
  3. 유지보수가 얼마나 용이한 지

1에서 당연히 C 언어는 루비에 비해 크게 뒤쳐질 수밖에 없습니다. 언어 자체의 특성도 그렇고, 외부 라이브러리의 사용도 쉽지 않으니까요. 하지만 제가 둘 다 경험해보니, 2의 측면에서 C 언어가 결코 루비에 비해 많이 뒤쳐지지는 않는 것 같습니다. 제가 조금 더 C 언어에 숙련된 프로그래머라면 그 격차는 더 줄어들겠죠? 그 이유는 다음과 같습니다.

  1. C에서는 쓸만한 외부 라이브러리가 많지 않습니다. 하지만 외부 라이브러리를 덜 가져다 쓴다는 말은 그만큼 관련 문서들을 찾아보는 데에 시간이 덜 걸린다는 이야기도 됩니다. 따라서 IDE와 웹브라우저 사이에서의 컨텍스트 스위칭에 시간을 덜 쓸 수 있습니다.
  2. 컴파일 언어라는 특성에서 비롯되는 IDE의 도움이 강력합니다. (자동 완성, 타입 체크, 오류 점검 등)
  3. 컴파일이 되기 때문에, 개발에는 시간이 더 걸릴 수 있지만 테스트와 디버깅에는 시간이 덜 걸립니다.

3의 경우에는 현재 상태에서는 말하기 힘든 부분이 있습니다. 왜냐하면 지금까지 C 언어를 통해 개발된 프로젝트들은 (1) 안정화되어서 커다란 스펙 변경이 없는 경우, (2) 신규 프로젝트라서 스펙이 미리 정해져 있는 경우, 둘 중 하나였기 때문입니다. 추후 애자일한 과정을 통해 잦은 스펙 변경이 있어야 할 때에 C 언어가 얼마나 유지보수가 용이한 지는, 조금 더 지켜보고 말씀드려야 할 것 같습니다. 다만 개인적으로는 코드 양이 더 많기 때문에 유지보수가 더 어렵지 않을까 하는 막연한 추측을 하고 있습니다. 아마 프로젝트 규모가 커질 수록 더하겠죠?

 

결론

 APR을 사용하면 C 언어로 모바일 API를 작성하는 일도 그렇게 어려운 일은 아닙니다. 약간 불편하기는 하지만 프로덕션 레벨에서 쓸 수 없을 정도는 아니고, 그만큼 우월한 성능을 얻을 수 있기 때문에 만약 스펙이 자주 바뀌지 않고 C 언어와 APR 기반 개발에 익숙하다면 시도해볼만 한 가치가 있는 것 같습니다.

지난 번 포스팅에서 많은 분들이 “CPU 사용률을 보면 사실상 CPU가 병목은 아닌 것 같다”고 지적해 주셨습니다. 웹 어플리케이션의 속도가 DB 속도에 의존적인 것은 사실입니다. 하지만 DB 병목보다는 어플리케이션 레벨에서의 불필요한 오버헤드 (정확히 말하자면 Hibernate의 eager loading) 역시 큰 속도 저하의 원인이었습니다. 이를 수정하기 위한 노력을 기울였지만 결국 포기하고 주요한 속도 저하의 원인이었던 ORM을 버리고 서버를 다시 개발하기로 결정하게 된 것입니다. 물론 이게 절대적으로 최선의 선택이었다고는 하기는 힘들지만, 목표한 성과를 이뤘다는 점에서 긍정적이라고 생각합니다.

모든 프로그래밍 언어와 프레임웍은 각자 제 용도에 맞는 쓰임새가 있다고 생각합니다. (너무 모범적인 결론이지만) 이음의 API 서버는 현재로서는 큰 설계의 변화나 스펙의 변화가 없기 때문에 유지 보수가 조금 불편하더라도, 퍼포먼스를 끌어낼 수 있는 C 언어를 사용했죠. 그리고 애초 의도했던 바와 부합하는 효과를 얻었습니다. 특히나 모바일 API 쪽은 HTML 페이지를 렌더링하는 웹 서버와 비교하더라도 많은 기능이 필요하지 않습니다. 만약 웹 서버를 개발하는 일이었다면 string 관련 함수들이 불편한 C를 사용하지 않았을 것입니다. 어떤 언어(프레임웍)을 쓰는 지도 중요하지만 지금 사용하고 있는 도구가 목적에 맞는 지도 중요하지 않을까요?

하지만 무엇보다도 개발자에게 그 도구가 얼마나 손에 익은 도구인지도 중요한 것 같습니다. 아무리 좋은 도구가 있더라도 사용할 줄 모르면 무용지물인 것처럼요. 이음에서는 APR 환경에서 오랜 시간동안 개발을 해왔던 개발자 분이 계셨고 그 분이 첫 삽을 뜬 덕분에 다른 분들이 수월하게 개발을 시작할 수 있었던 것 같습니다. 그리고 개발자들이 대부분 컴퓨터 공학 전공 베이스를 가지고 있었던 덕분에 C 언어를 생소하게 느끼지도 않았구요. 이 글을 읽고 APR 도입을 고려하고 계시다면, 이 점을 다시 고려해보세요 🙂

 About the use of language: it is impossible to sharpen a pencil with a blunt axe. It is equally vain to try to do it with ten blunt axes instead ; 프로그래밍 언어의 사용에 관하여: 무딘 도끼를 가지고 연필을 깎는 일은 불가능하다. 무딘 도끼 10개를 가지고 시도한다고 해도 결과는 마찬가지다.  – E. W. Dijkstra

 

TabsSpacesBoth

그래도 한 프로젝트에 여러 언어는 섞어 쓰지마세요. 술 섞어 먹는거랑 똑같습니다. 먹을 때만 행복

 

Screenshot at Jan 21 18-52-44

 

아파치 모듈로 개발된 API 서버, 이음 베이론을 소개합니다.

안녕하세요. 저는 이음소시어스에서 서버사이드 개발을 하고 있는 오영식(다람)입니다. 개발팀 블로그의 첫 글로 우리의 새로운 API 서버 마개조 프로젝트, 이음 베이론을 소개해 드리려고 합니다.

Introduction

2년쯤 전, 카카오톡에서 “겁나빠른황소” 프로젝트를 적용하였습니다. 초반에는 빠르게 작동하던 카카오톡이 사용자가 늘어남에 따라 속도 또한 느려져 전체적으로 시스템을 개편한 것입니다. 이전부터 카카오톡을 사용해 보신 분들은 아시겠지만, 메시지 전송을 할 때 항상 보이던 바람개비가 그 이후에는 구경조차 어렵게 바뀌었습니다.

이와 비슷하게, 이음 또한 사용자가 많이 늘어나면서 수많은 데이터가 쌓여감에 따라 빠릿빠릿하게 작동하던 어플리케이션이 점점 느려져 사용에 불편함을 느낄 정도가 되었습니다. 또한, 기존에 개발되어 있는 소스코드에 계속해서 기능을 추가하다 보니 소스코드의 양이 너무 방대해져 유지보수가 어렵게 되어, 전면적인 소스코드 개편을 고려하게 되었습니다. 그 결과로 만들어진 것이, 이름하여 베이론 프로젝트입니다.

베이론?

먼저 전혀 중요하지 않지만 베이론이라는 이름은 매우 빠른 서버를 만들자는 취지에 따라 구글에서 자동차 제로백 순위로 검색해 나름 있어 보이는 이름으로 지었습니다.

 

1

실제로 소스코드 저장소 위키에도 이렇게 써 있습니다.

 

베이론은 아파치 웹 서버의 모듈로 개발이 되어 있습니다. 물론 C로 개발하고 있고, 이 때문에 기존의 Spring framework를 사용한 서버보다 훨씬 빠르고 더 적은 리소스로 구동이 가능합니다.

맨 처음 개편을 고려할 때 많은 언어들을 고려했습니다. Rails, PHP, Python, Node.js, Java로 다시만들기(!) 등 많은 선택지가 있었는데, 아파치 모듈(C)를 선택한 이유 몇 가지를 추려보면:

  1. 웹 개발 언어가 많은 연산을 필요로 하지는 않고 대부분의 병목이 DB에서 남에도 불구하고, 생각보다 많은 속도 저하가 웹 어플리케이션 내부에도 존재합니다. C는 (잘 짜면)매우 좋은 성능을 보장해줍니다.
  2. 좋은 성능에도 불구하고 메모리 관리와 같이 컨트롤하기 까다로운 문제도 존재하고, 데이터 저장 및 수정이 타 언어에 비해 불편한 것이 사실입니다. 하지만 아파치가 내부적으로 사용하는 라이브러리(APR)이 매우 강력해 C로도 충분히 빠른 시간 안에 좋은 퍼포먼스의 웹 어플리케이션을 개발 할 수 있습니다.
  3. 스프링, 레일즈와 같은 프레임워크는 웹 개발자에게 매우 편리한 강력한 기능들을 많이 제공하고 있습니다. 하지만 프레임워크는 아무리 좋은 성능을 내더라도 프레임워크 없이 잘 짜여있는 어플리케이션보다 느린 것이 사실이고, 처음에 익히는데 시간이 드는 것이 단점입니다. 따라서 프레임워크에 의존하지 않는 방식으로 개발을 하게 되었습니다. 언젠가 스프링 배치의 퍼포먼스와 개발속도에 분노해 아예 바닥부터 짜버린 배치의 사연도 소개하게 되겠지요.
  4. 사실 C로 API를 구현한다는 사실이 매우 생소하긴 하지만, (NHN에서 통합검색 서버를 개발하신 제 옆의 모 개발자님께서 말씀하시길) 수많은 트래픽을 감당하고 있는 네이버 검색 웹 서버 또한 대부분 아파치 모듈로 개발되어 있어, 이는 충분히 사용할만하고 검증되어 있는 방식이라고 할 수 있습니다.

벤치마크

베이론 서버와 기존 Java로 개발된 서버는 많은 부분에서 퍼포먼스 차이를 보여줍니다. 이 글에서는 두 가지 관점에서 비교를 해 보겠습니다. 첫 번째는 사용자가 직접 체감하게 되는 Latency이고, 두 번째는 서버 개발자들이 항상 전전긍긍하며 바라보게 되는 CPU 사용률입니다. 먼저, Latency를 비교해 보도록 하겠습니다.

(1)   Latency

    1. Java 서버
2

Java 서버의 Latency

 

대체로 평소에는 0.1~0.2초대의 응답속도를 보여줍니다. 그렇지만 우리 서비스의 특성상 특정 시간에 매우 많은 트래픽이 발생하는데, 이 시간대에는 0.7초까지 응답속도가 늦어지는 것을 볼 수 있습니다.

      1. 베이론 서버
3

베이론 서버의 Latency

전체적으로 응답속도가 0.1초 아래로 나온다는 점도 훌륭하지만, 주목하셔야 할 점은 서비스 이용자가 몰리는 시간과 그렇지 않은 시간의 차이가 전혀 없다는 사실을 보아주세요. 어떠한 서비스던 그렇겠지만, 우리처럼 특히 특정 시간에 많은 트래픽이 몰리는 서비스의 경우에는 이러한 항상성(?)은 서비스의 큰 안정성을 보장하게 됩니다.

(2)   CPU 사용률

      1. Java 서버
4

Java 서버의 CPU 사용률

기본적으로 차지하는 CPU 사용률도 최소 5%이고, 이용자가 몰리는 시간에는 특히 30~40%대의 CPU 사용률을 가지고 있습니다. 따라서 안정적인 서비스 제공을 위해서는 좋은 CPU를 가진 인스턴스를 이용할 수 밖에 없습니다.

      1. 베이론 서버
5

베이론 서버의 CPU 사용률

굉장히 들쭉날쭉한 것 같아 보이지만, 평소에는 거의 0%대의 CPU 점유를 하고 있다가 사용자가 몰리는 시간에는 무려 3%까지(!!!) 차지하고 있습니다. 사실상 CPU는 거의 놀고 있습니다. Java서버와 비교하면 정말 큰 차이가 드러나고 있습니다.

(3)   기타

위 벤치마크 자료에는 드러나 있지 않지만 더 무서운 사실이 존재합니다. Java서버는 이용자가 몰리는 시간에 자동으로 인스턴스가 4대로 변경되며, CPU사용률에 따라 6대까지 늘어납니다만, 베이론 서버는 단 2대로 운영이 되고 있습니다. 이 사실까지 감안한다면, 베이론 서버의 압승이라고 볼 수 있겠지요.

단점도 있지 않을까요?

음, 사실 저도 베이론 프로젝트를 맨 처음 들었을 때 복잡한 C로 작성한다는 것이 매우 두려웠는데, 막상 진행을 하다 보니 아파치에서 제공하는 라이브러리를 통해 정말 쉽게 작성할 수 있었습니다. 그나마 있는 단점이라고 하면, 조금 생소하다는 점이 있겠네요. 아파치 모듈로 API를 작성하는 경우는 거의 없으니까요. 또, 필요한 기능이 있으면 직접 구현(!)해야 한다는 단점도 존재합니다.

그러나, 여전히 C는 API를 구현함에 있어 정말 좋은 언어입니다. 이 글에서는 단순히 아파치 모듈의 설명과 벤치마킹 자료만 올려 놓았지만, 나중에 추가로 기본적인 아파치 모듈의 작성 방법에 대해 글을 작성할 수 있을 것 같습니다.

기타

C로 개발하면 API는 몰라도 웹은 어렵지 않을까? 하는 생각을 하실 수도 있겠다는 생각이 들었습니다. 우리는 API를 C로 개발한 뒤에, Front는 Ruby 혹은 Python처럼 빠르고 간결하게 작성할 수 있는 언어로 개발한 뒤 API를 호출하여 결과값을 얻어내는 방식을 고려하고 있습니다. 이 부분은 레진코믹스의 경우와 비슷한데요, 위의 링크를 참고하시면 좋을 것 같습니다. 이렇게 작성하면 C의 빠른 속도와 스크립트 언어의 빠른 개발 속도를 모두 취할 수 있다는 장점이 있습니다. 물론, 웹에서도 API를 이용하기 용이하도록 잘 설계했다는 전제조건이 있어야겠지요.

결론

 

 …Write in C

Q: 모바일 api가 느린데 어떻게 하면 좋을까요?
A: C로 짜면 됩니다.

11