서버 비용 아끼는 법

클라우드 서비스에 첫 가입 후 제공받는 달달한 소규모의 크레딧으로 첫 VM을 띄우며 개발을 시작했던 때가 떠오른다. AWS 같은 경우는 스타트업 프로그램이 잘 되어있고 너무나도 친절하게 자사의 서비스들을 십분 활용할 수 있도록 초기 아키텍처를 잡는 방법들도 알려준다. 여기에 더 나아가 대부분의 대형 클라우드 업체에서는 스타트업을 위해 추가로 크레딧을 제공한다. 커리어 대부분을 스타트업에서 보냈던 나한테는 스타트업과 클라우드 서비스의 조합이 너무나 자연스럽다. 라이너에 들어와서 처음으로 GCP를 쓰며 AWS 대비 상대적으로 저렴한 비용과 어렵게 따낸 스타트업 지원 프로그램 덕분에 행복했다. 그들은 자선사업가인가? 하며 서버 비용 무서운줄 모르던 때였다. 클릭 몇 번으로 그져 가져다 쓰면 되는 클라우드의 화려한 서비스에 홀로 백엔드를 관리했던 나는 약간의 야근만으로도 전체 인프라를 관리할 수 있었다.

모든 아키텍처를 통틀어 CPU 20~30개면 충분하던 시절을 지나, 유튜브 하이라이트, 구글 검색 보조, 추천 피드 등이 나오며 하나의 API에서만 CPU를 20개씩 쓰는 시기가 도래하였다. 트래픽은 더 늘면 늘었지 줄어들 일은 없을 것 같았다. 마케팅을 비롯하여 사업의 성장을 위해 회사의 비용이 점점 커지는 상황에서 서버 비용은 눈덩이처럼 커져갔다. 라이너 창업자 중 한 명인 브라이언이 초롱초롱한 눈을 하고 내 자리로 찾아오면 나는 이내 서버 비용 이야기를 하러 왔구나 생각하며 눈을 찔끔 감았다. 나조차도 점점 늘어나는 서버 비용에 대해 부담을 느끼고 있는 상황이니, 브라이언은 아마 더 크게 느끼고 있었을 것이다. 최대한 자세하게 서버 비용이 늘은 이유에 대해 설명하고 이내 설명이 끝나면 브라이언은 알겠다고 하며 꼭 좀 부탁한다는 말을 남기고 자리로 돌아갔다.

옛날 GCP 대시보드에는 전체 인프라의 CPU 활용도를 볼 수 있었다. 당시 서버 비용이 마구 늘어나던 때의 활용도는 10% 정도였다. 사실 서비스 개발을 하는 가장 큰 이유는 코드 한 줄이 만들어내는 경제적 가치 때문이 아닌가. 만들어내는 경제적 가치를 극대화시키기 위해서는 그 비용을 줄일 필요성이 있다. 그리고 효율을 중시하는 엔지니어링 마인드와도 크게 다르지 않다고 생각했다. 서버 비용을 관리하는 것은 개발자의 의무이자 역할이다.

오직 성장에만 집중했던 인프라를 이제는 다시 돌아볼 때가 왔다. 어느 부분에서 서버 비용 절감이 가능할지, 하드웨어의 활용도를 높일 수 있을지 확인해야 했다. 이 글은 유휴 리소스를 사냥하기 위해 삽질했던 흔한 경험의 나열이다.



DB 직접 운영하기 VS 클라우드가 제공하는 것 쓰기

많은 개발자들이 기존 아키텍처에 녹아들지 못할만큼 새로운 기능을 개발하는 경우, 혹은 완전히 새롭게 서비스 개발을 시작하는 경우에 이러한 선택들을 빈번하게 마주친다. 라이너는 전자를 선택한 회사였다. 내가 라이너에 들어오기 훨씬 전에 이미 GCP에 엘라스틱 서치 클러스터와 MariaDB 복제 클러스터가 존재했다. 이전까지의 스타트업에서는 클라우드 업체가 제공하는 관리형 서비스를 사용했기 때문에 경제적 장단점을 비교해서 생각하기 좋았다.

DB 운영 비용이 핵심

여느 때와 다름 없이 슬로우 쿼리를 잡던 평범한 어느 날, 같이 일하는 백엔드 팀원 토니가 IGNORE INDEX라는 것이 있다고 말해주었다. 나는 상당히 흥분했었는데 그 이유는 DB의 수많은 테이블에 있는 어떻게 쓰이는지 모를 인덱스들을 관리할 수 있을 것 같았기 때문이다. 이 옵션은 인덱스 빌딩은 계속 해주면서 옵티마이저가 쓰지는 못하게 해주는 일종의 플래그이다. 잠깐 IGNORE 시켰다가 슬로우 쿼리가 잡히면 ‘아 이거 쓰는거였구만ㅎㅎㅎ’ 하고 IGNORE를 꺼버리면 끝이었다. 만약 일정 기간 동안 별 이상 없다면? 인덱스를 DROP하면 되는 것이었다. 나는 기쁜 마음에 기술 문서로 들어가서 명령어를 복사하고 콘솔에 들어가서 실행시켰다. 근데 문법 오류가 나는 것이었다. 그렇다. 버전이 맞지 않는 것이다. 어차피 읽기 작업은 복제 DB에서만 일어나니까 좀 귀찮기는 해도 수작업 롤링을 하면 버전 업그레이드가 불가능하지는 않았다. 그렇지만 지금 할 일이 눈앞에 쌓여있는 상황에서 해도 티가 거의 안나는 작업을 한다는 것은 시험기간에 당장해야할 공부는 안하고 책상 정리를 하는 것과 비슷했다. 이내 할 일에 파묻히며, 관리형 서비스를 사용했다면 클릭 한 두번에 끝날 일이 이렇게 어렵구나 싶었다.

사실 이 정도 일은 점심시간 때 웃고 넘어갈만한 아주 작은 일이다. 좀 더 심각한 일도 있었다. 바로 마스터 DB의 CPU가 80-90을 치던 시기였다. 더 큰 문제는 마스터의 IO 속도가 떨어져서 애플리케이션 서버의 커넥션들이 다 타임아웃이 났었다. 이 때문에 회원가입이 잘 안됐고, 하이라이트가 10개중 2개는 사라졌으며, 라이너 메인 페이지 접속이 잘 되지 않았다. 이 때만큼 관리형 서비스가 간절했던 때가 있었을까 싶다. 다행히 디스크를 교체하는 작업은 어렵지 않았다. VM 재시작 없이 가능한 방법이 있었다. IO가 잡히자 전체적으로 서버 로드가 줄어들었다. 그렇지만 이내 트래픽이 더 늘어나면 분명 큰일이 날 것 같았다. 결국 중대 결정을 내렸다. 트래픽이 가장 적은 시간 대에 잠시 마스터를 내리고 CPU와 RAM을 업그레이드하는 것이었다. 나는 해당 시나리오를 분 단위로 정리했고 카피 서버를 통해서 리허설을 진행했다. 다음날 오전 7시쯤, 나와 토니는 정해진 시나리오를 따라가며 마스터를 업그레이드 했다. 원래는 15분 정도를 예상했는데 거진 40분 정도가 지나서야 서비스가 다시 라이브 됐다. 역시나 리허설과 실전을 다르구나 싶었다. 지금도 그때를 생각하면 참으로 무식하고 용감했구나 싶다.

지금까지 말한 사례 외에도 관리형 서비스였다면 겪지 않을 수많은 운영 이슈가 있었다. 그래서 결론이 관리형 서비스를 써라! 라는 것일까? 그렇지는 않다. 지금 사용하는 복제 클러스터를 관리형 서비스로 동일하게 올리면 비용이 4~5배 정도 된다. 이러한 차이가 매달 누적된다면 무시할 수 있는 수준을 넘어서게 된다. DB의 운영 불안정성은 백엔드와 프론트의 기술로 커버가 가능하다. 예를 들면 서버가 불안정하면 잠깐 요청을 퍼징시키거나, 무거운 콜들을 잘게 쪼개서 보낸다거나 하는 방식으로 말이다. 물론 이런 것들은 추가적인 개발에 해당되지만 현금보다 사람이 더 풍부한 리소스인 스타트업의 입장에서는 우선순위가 뒤바뀌기도 한다. 돈이 굉장히 많고 조금의 서비스 멈춤도 허용할 수 없는 업이라면 마찬가지로 다른 형태의 우선순위가 적용될 것이다. 그러니 흔하디 흔한 결론 밖에 내리지 못해 미안함이 크다. 결국은 상황에 따라 다르다는 것. 하나만 덧붙이자면 비용이 문제가 안된다면 관리형 서비스를 권한다. 물론 비용이 항상 문제지만.




쓸데 없이 낭비되는 디스크

이제 오토스케일링은 너무나 흔해졌고 없으면 이상할 정도로 필수 기능이 되었다. 라이너에서는 쿠버네티스도 사용하고 인스턴스 그룹 기반의 오토스케일링도 사용하고 있다. 문제는 이렇게 늘었다 줄었다 하는 인스턴스들이 쓰는 디스크의 타입이 쓸데 없이 좋은 것이라는 것에 있었다. GCP의 경우 4가지의 디스크 타입을 지원하고 있다. 그 중에서 standard persistent disk가 제일 구린 타입이다. 구린 기준은 IO 퍼포먼스이다. 제일 좋은 것은 당연히 ssd persistent disk이다. 가격은 딱 IO 퍼포먼스에 비례한다. 문제는 이 양극단의 타입 사이에 있는 balanced persistent disk와 extreme persistent disk이다. 이 두 타입으로 인스턴스를 만들면 SSD 사용 비용이 같이 청구된다. 즉 이 두 타입은 ssd와 standard의 짬뽕 타입인 것이다. 그리고 standard에서 balanced로 한단계만 높여도 비용이 2.5배가 된다. 심지어 GCP는 VM 생성시 기본으로 balanced가 선택되어 있다. 이걸 모르고 그냥 VM을 생성했다간 덤탱이를 맞는 것이다.

라이너의 절반 정도 되는 인스턴스들이 이렇게 덤탱이를 맞은 상태였고 이에 높은 IO가 필요 없는 경우에 대해서 모두 standard 타입으로 변경했다. 이를 통해 말 그대로 절반 정도의 디스크 비용이 감소했다. 그리고 이 감소분의 일부를 DB 디스크에 역으로 투자하면서 서비스도 더 안정되는 결과를 얻을 수 있었다. 결과적으로는 2.5배의 비용 감소를 이루지는 못했지만 이전보다 30% 정도 디스크 비용을 줄이면서 선택과 집중을 할 수 있었다.



서버 태스크 별 적정 CPU 찾기

앞서 라이너의 전체 CPU 활용률이 10% 정도라고 했는데, 이 글을 보시는 많은 분들이 너무 방탕하게 리소스를 쓰는 것이 아닌가 생각하셨을 것이다. 나조차도 해당 숫자를 파헤치기 전, CPU를 물 쓰듯이 쓰는 것 아닌가하는 자책과 반성을 했다. 그리고 어느 부분에서 CPU 활용도를 높일 수 있을지 하나하나 뜯어봤다. 활용률이 굉장히 낮은 대표적인 서버 그룹은 다음과 같았다.

  • 테스트성 서버
    • 개발을 위해 VM을 활용하는 경우
  • 인프라 모니터링 + 관리 서버
    • 젠킨스
    • 키바나
  • 데이터 분석 / 대시보드 서버
  • 배치 작업 서버
  • 실시간 데이터 처리 서버

비중으로 따지자면 배치 작업 서버와 실시간 처리 서버의 비중이 높았다. 배치 작업의 경우는 해봤자 5분 단위로 돌고 있어서 유휴인 경우가 많았다. 보통 이런 경우 서버리스 형태의 제안을 많이 해주시는데, 배치 작업이 생각보다 커서 단순히 하나의 실행 콘텍스트로는 처리가 힘들었다. 결국 최근 30일 동안 최대로 많이 쓴 CPU를 확인해보고 P90정도로 인스턴스 스펙을 조정했다. 평균적으로 절반의 CPU와 메모리로 재조정했다.

실시간 데이터 처리 서버의 경우는 GCP에서 제공해주는 서비스를 쓰고 있었는데 기본적으로 높은 사양의 인스턴스로 구성됐다. 이 부분을 실시간으로 수정할 수 있는지 봤는데 불가능했다. 낮은 비용으로 치환하기 위해서는 파이프라인을 다시 만들어야 했는데 이 역시 부담으로 다가와서 실행하지 못했다.

나머지 테스트용 서버 및 관리 서버의 경우에는 모두 스펙을 다운시켰다. 그 과정에서 조금 잡음이 있긴 했지만 슬랙을 통해 팀원들의 양해를 구했다. 이렇게 부하 몹들을 잡고 나니 보스 몹을 잡을 여력이 조금 생겼다. 이제 API 서버 최적화에 나설 차례였다.

CPU 활용도만 무조건 높이면 발생하는 일

가장 먼저 했던 조치는 오토스케일링 기준을 높이는 것이었다. 이전에 하도 자질구레한 장애가 빈번히 발생하기도 했고 회원 유입 마케팅에 큰 비용을 썼기 때문에 CPU 기준을 굉장히 낮춘 적이 있었다. 이제는 어느정도 서버가 안정됐다고 생각해서 활용도를 높이기 위해 오토스케일링 CPU 기준을 높이고 그룹당 최소 인스턴스 수를 낮췄다. 비슷한 설정을 hpa를 통해 쿠버네티스에도 적용했다. 갑자기 확 줄어든 인프라 사이즈를 보니 마음이 흐뭇했다. GCP의 비용 산정은 하루 정도 딜레이가 있어서 내일 확인할 비용을 생각하며 행복에 잠겨있었다.

이렇게 쉬운 것이었다면 얼마나 좋았을까. 문제는 저녁에 트래픽이 늘면서 발생했다. CPU 기준이 높아지자 새로운 인스턴스가 올라오는 타이밍이 기존 인스턴스의 로드가 높은 상황에서 이루어졌고, 새로운 인스턴스가 올라오면 기존 인스턴스들이 먹통이 되었다. 이 때문에 새로운 인스턴스가 아무리 많이 떠도 전체적으로 가용 인스턴스의 수가 늘지 않았다. 이는 곧 중형 장애로 이어졌고 나는 급하게 인스턴스의 수를 확 늘렸고 동시에 오토스케일 기준을 다시 낮추었다. 이내 인프라는 안정됐지만 오전에 취했던 조치는 모두 무효가 되었다. 기준을 높이는 조치가 유효하려면 결국 인스턴스의 수가 트래픽을 미리 예측해서 늘어나 있거나, 높은 로드에도 잘 견디는 서버를 구축하는 수밖에 없었다. 쿠버네티스에서도 비슷한 일이 일어났고 결국 서버 개발자는 근본으로 돌아오게 됐다. 서버를 가볍게 짜는 것이 곧 비용을 줄이는 일이라는 것을 뼈저리게 느낄 수 있었다.

도와줘요 JVM!

기존의 노드 기반 서버에서 JVM 기반의 서버로 이전하는 것을 생각했고 이를 팀에 제안했다. 결론부터 말하자면 JVM 기반 서버로 포팅하면서 정확히 2.5배 더 효율적으로 리소스를 쓰게 됐다. 높은 트래픽을 더 잘 견디는 것은 물론 타입 시스템이 주는 편안함도 누릴 수 있었다. 언어는 코틀린을 선택했다. 자바는 너무 올드하고 스칼라는 너무 현학적이라고 생각했기 때문이다. 쉽지 않은 과정이었지만 노드 서버에서 레이턴시가 불안정한 API를 한두개씩 코틀린으로 포팅했다. 서버는 점점 안정되어갔고 활용되는 전체 서버의 수도 절반 정도 줄었다. 오토스케일링 기준도 예전처럼은 아니지만 15%정도 더 높일 수 있었다. 가장 빠른 길은 가장 바른 길이라는 말이 생각났다. 아무리 CPU와 RAM이 넘쳐나는 시대라지만 결국 개발자는 좋은 코드와 로직을 짜야한다. 그리고 우리나라의 수많은 IT 회사들이 JVM 기반으로 일하는 이유도 어렴풋이 알 수 있었다.

이러한 일련의 과정을 통해 전체 인프라의 CPU활용도는 30% 정도 수준까지 올라갔다. 개발자의 능력이 증가할 수록 이 활용도는 더 높아질 것이라 생각한다. 아직 퍼포먼스 최적화를 할 수 있는 부분들이 많이 남아있다. 특히 추천 검색 인프라쪽에 파이썬 코드들이 많이 있는데, 다른 언어로 포팅되거나 퍼포먼스를 강화해줄 intepreter로 교체하면 비용을 더 줄일 수 있을 것이라 생각한다. 결국 해당 서버의 task를 고려하여 트래픽과 리소스의 관계를 파악하고 단순히 리소스가 과할당된 것인지, 최적화의 여지가 남았는지, 트래픽이 너무 들쭉날쭉한지를 모두 고려할 때 적절한 스케일링 팩터를 결정할 수 있다. 쉬운 일이 하나도 없다.

이외에 GCP를 쓴다면 고려해야할 요소들은 다음과 같다.

약정 구입 고려

GCP에는 약정이라는 형태로 예약 인스턴스를 제공한다. N2타입의 경우 CPU 클럭이 N1보다 훨씬 좋음에도 약정 구입시 같은 가격에 인스턴스를 활용할 수 있다. N1을 사용하되, 서버 사용량을 보면서 꽤나 보수적으로 N2 약정 구입을 고려하는 것을 추천한다.

Public IP 할당 조심

위에 언급된 디스크 타입 외에, GCP의 공개 IP를 끄는 옵션은 상당히 숨어있으며 기본적으로 ON으로 되어있다. 인스턴스의 수가 늘어날 수록 이 비용이 무시못할 정도로 커지니 외부 망에 접속이 필요한 경우 NAT 서버를 통해 이루어지도록 하고 public IP 할당을 꺼두도록 하자. 다행히 Cloud NAT라는 서비스가 제공되며 클릭 몇번으로 세팅이 가능하다. 라이너의 경우 거의 모든 인스턴스가 다 public IP를 갖고 있어서 대청소를 시행한 적이 있다.

빅쿼리 슬롯 구입

데이터의 양이 점점 많아지고 제품 방향을 결정하기 위한 인사이트가 계속해서 필요해지며 빅쿼리의 활용도가 날이 갈수록 높아져갔다. 온디맨드 형식으로 쓰다보니 빅쿼리의 가격이 어느새 서버 비용 지출 다섯 손가락 안에 들어왔다. 예약 인스턴스와 비슷하게 빅쿼리도 약정 계약이 가능하다. 빅쿼리가 쿼리나 task로 소진하는 리소스를 slot이라는 단위로 표현해주는데, 이 슬롯을 계약하는 형태이다. VM 약정과는 다르게 꽤나 유연하게 변경이 가능하니 비용 추이를 보며 적정선을 찾으면 된다.



마무리

서버 개발이 많이 쉬워졌다고 하지만 오히려 위임하게 되는 것들이 많아지며 놓치는 것도 늘어나고 있다. 내가 작성하는 코드의 경제적인 가치를 항상 염두하자. 서버 비용이라는 것은 결국 내가 작성한 코드의 효율과 직결된다. 개발 속도, 퀄리티에 이어 우리는 반드시 로직을 실행하는 하드웨어의 효율을 놓쳐서는 안된다. 특히나 스타트업의 경우는 소진되는 현금 흐름이 생존과 아주 밀접한 연관이 있으니 우선순위도 높다. 서비스 급성장의 정신 없는 구간을 지나가면 반드시 챙겨야할 것들을 잊지 말아야 한다.

생각이 많아져 행동하기 어려워질 수 있다. 수많은 트레이드 오프를 경험하며 어느 선까지 해야할지 고민이 될 것이다. 내 생각엔 그저 아는만큼 치열하게 생각하고 실제로 인프라가 보여주는 지표와 퍼포먼스를 보며 다시 대응하면 된다. 대체로 정답은 실제 세상에 있다. 한 방에 아주 망할 정도로 잘못된 판단을 하지 않는다면 분명 어느정도의 오차는 빠른 시간 안에 대응할 수 있을 것이다. 현재 인프라 상황에 대해 제일 잘 아는 것은 우리 팀이니, 우리가 치열하게 고민한다면 최선의 답을 찾을 수 있을 것이라 믿어야 한다. 그리고 문제가 생기면 대응하면 된다. 그렇게 하다보면 언젠가는 되어있으리라 생각한다.