“홈페이지가 죽었어요!”

입사 이래 몇 달에 한 번씩은 꼭 들리는 문장이다. 사실 정말 무서운 문장이다. 무언가 안되는 것 같기는 한데, 무엇이 문제여서 일어난 사건인지 혹은 정말 문제는 맞는지 아무것도 알 수 없으면서도 일단 불안감이 몰려오기 때문이다. 이유가 무엇이든 개발자는 제품의 문제를 해결해야 하는 사명을 지니고 있으므로 만사를 제쳐두고 원인을 파악하고 조치하기 마련이다. 한동안 잠잠하다 싶더니 저번 주에도 결국 저 무서운 문장을 듣고 말았다. 그것도 가장 중요한 순간인 새로운 채권이 올라오고 안내문자와 알림톡을 받은 수많은 투자자분들이 접속하는 타이밍에 듣고 말았다. 안그래도 트래픽이 몰릴 때 홈페이지의 로딩 속도가 현저히 느려지는 현상을 인지하고 있어서 한 두 분이 짬짬히 성능개선 작업을 진행하던 중이었다. 그런데 이번에는 느려지는 정도가 아니라 Nginx의 정말 못생긴 기본 에러페이지인 ‘404 Not Found’ 혹은 ‘502 Bad Gateway’와 같은 페이지가 보이는 것이었다. 그나마 다행인 것은 서버가 완전히 다운되는 것은 아니고 일정 수의 사용자들에게는 동작하는 모습이 보이기도 했고, 현저히 느리기는 하지만 트래픽이 몰리는 약 10~20여분 정도가 지나면 멀쩡하게 돌아오고 있었다. 결국 전체 개발팀이 모든 업무를 멈추고 성능개선 작업에 돌입하기로 했다.

성능문제의 원인파악

소프트웨어의 성능문제의 원인을 파악하는데는 여러가지 도구와 지표들이 있겠지만, 제품의 거의 대부분이 AWS 인프라스트럭쳐 위에 올라가 있으며 주 웹프레임웍으로 Django를 쓰고 있는 피플펀드로써는 선택지가 별로 없다. 사실 가장 큰 문제는 하루에 단 한 번, 상품이 올라오는 타이밍에 10~20여분간 그 외 시간대비 20~30배 의 트래픽이 몰린다는 점이다. 당연하게도 성능개선의 목표는 이 타이밍에 막혀있는 곳을 뚫어주는 것이며, 어떤 곳이 어떻게 막혀있는지 정확하고 빠르게 문제점을 찾아내는 것이 중요하다. 피플펀드에서는 다음과 같은 도구를 활용하여 문제가 될만한 지점들을 설정하고 성능개선 목표를 잡았다.

AWS CloudWatch

AWS의 CloudWatch는 전체적인 문제를 파악하는데 가장 간편한 도구이다. CloudWatch의 Dashboard를 하나 만들어두고 ‘CloudFront의 에러발생 빈도’, ‘ELB가 받는 Request 수’, ‘EC2 인스턴스의 디스크 공간’, ‘RDS CPU 사용율’ 등을 설정해두어서 전체적인 상황을 체크하고 있다. 몇몇 지표들은 threshold를 설정해두고 이 수치를 넘기게 되면 이메일과 슬랙을 통해 알림을 주도록 해두었다. CloudWatch를 살펴본 결과 피크타임 동안 ELB에서 HTTP Status가 400 혹은 500번대로 응답하는 비율이 25%에 육박했고, RDS의 CPU 사용률은 90%가 넘어가 있었으며, EC2 인스턴스들은 스왑메모리를 2GB 이상 사용하고 있었다.

CloudWatch - ELB의 에러응답 비율. 개선작업 이후 현저히 줄어들었다. CloudWatch - ELB의 에러응답 비율. 개선작업 이후 현저히 줄어들었다.

New Relic APM

New Relic APM은 애플리케이션의 성능을 측정하는데 있어서 가장 강력한 도구이며, 다양한 언어를 지원한다. 처음에는 무료인 설치형을 사용하고 있었지만, 소수의 인원으로 관리가 힘든 문제를 극복하지 못하고 Cloud 버전으로 갈아타게 되었다. 몇 명의 개발자가 처리할 작업을 대신해주기 때문에 유료버전 사용가치가 충분하다. 다양한 기능들을 제공하지만, 그 중에서도 초기 성능개선 작업을 하는데는 Transactions 모니터링 기능이 활용도가 높다. 크게는 API 단위로 측정을 하는데, API가 한 번 호출되었을 때 내부에서 사용하는 함수 단위, 데이터베이스 접근 단위로 평균 사용시간도 상세히 알려준다. 가장 많은 시간을 사용하는 API 순으로 정렬해주는 기능이 있기 때문에, ‘Top 5 API 성능 개선하기’와 같은 목표를 잡고 개선작업을 진행하기에 용이하다.

New Relic APM - 느린 API들이 평소에 비해 요청 하나를 처리하는데 얼만큼의 시간을 사용하는지 보여준다. New Relic APM - 평소에 비해 느린 API들이 요청 하나를 처리하는데 얼만큼의 시간을 사용하는지 보여준다.

New Relic APM - 가장 많은 시간을 사용하는 API를 보여준다. 클릭을 해보면 데이터베이스 접근, 함수 단위로 각각 얼마나 시간을 사용하는지도 알려준다. New Relic APM - 가장 많은 시간을 사용하는 API를 보여준다. 클릭을 해보면 데이터베이스 접근, 함수 단위로 각각 얼마나 시간을 사용하는지도 알려준다.

Web Browser

브라우저의 개발자도구 또한 좋은 도구이다. 크롬은 다양한 개발자도구들을 제공해주기 때문에 특히 프론트 영역에서의 개선점을 찾기 수월하다.

Chrome Developer Tool Network Tap - 웹페이지를 로딩하는데 가장 많은 시간이 소요되는 구간을 빠르게 파악하는데 도움이 된다. Chrome Developer Tool Network Tap - 웹페이지를 로딩하는데 가장 많은 시간이 소요되는 구간을 빠르게 파악하는데 도움이 된다.

Chrome Developer Tool Audits Tap - 웹 앱의 성능 문제점 보고서를 만들어주며, 더불어서 베스트 프랙티스까지 알려준다. Chrome Developer Tool Audits Tap - 웹 앱의 성능 문제점 보고서를 만들어주며, 더불어서 베스트 프랙티스까지 알려준다.

선행 작업

구체적인 목표 설정

개발팀이 모두 함께 성능개선에 뛰어들기로 결정한 뒤, 즉시 회의를 거쳐 각자가 진행할 목표와 방향성을 논의했다. 그 결과 다음과 같은 목표를 잡을 수 있었다.

  • New Relic APM의 ‘Most time consuming’ 페이지를 기준으로 TOP 5 API를 순위권에서 없애기
  • ELB 에러비율 낮추기: 400 혹은 500번대의 에러코드가 너무 큰 비율로 반환되고 있는 부분을 해결하기
  • EC2 인스턴스의 사양을 높이고 수를 늘려서 부하 대비하기

다른 부서와 이슈 공유 및 작업시간 확보

사실 다른 부서에게 개발팀이 어떤 일을 하고 있는지 알리는 것 또한 기술적인 부분만큼이나 중요한 일이다. 다른 부서로부터 신뢰성을 확보하지 못하면 향후 협업에서 어려움을 겪을 수 있기 때문이다. 다행히도 피플펀드에는 다른 부서와 개발팀 사이의 중간다리 역할을 해주고 있는 PM 분들이 있다. 성능개선 목표를 설정하는 회의에도 PM분들이 함께 참석해서 이슈에 대한 대략적인 파악과 개발자가 아닌 분들도 이해할 수 있는 수준의 개선 전후 지표를 설정하는데 도움을 주셨다. PM분들과 공유했던 중요한 사항들은 다음과 같다.

  • 성능문제, 특히 오류 페이지를 보는 것은 전체 사용자가 겪는 현상이 아니다.
  • 딱 하나의 병목을 처리하여 해결할 수 있는 문제가 아니며, 앞으로도 지속적으로 도전받을 것이고 꾸준히 시간을 할애하여 개선해나가야 한다.
  • ‘개선작업 이후 피크 타임에 직접 홈페이지에 접속해서 속도 체감하기’와 ‘피크 타임의 에러비율을 개선작업 전후로 비교하여 공유하기’를 성과지표로 삼는다.

개선 작업

API 성능개선

피플펀드의 개발팀은 각자의 기술적 배경이 다르기 때문에 초반 러닝커브가 낮고 빠르게 개발할 수 있는 Django를 백엔드 웹프레임웍으로 사용하고 있다. 그러나 Django를 깊게 알지 못한 상태에서 초반에 만들어두었던 API들의 문제가 불거지게 되었다. API 성능개선 시 살펴본 주요한 부분들은 아래와 같다.

  • QuerySet.select_related()와 QuerySet.values(): Related object 들을 for loop 내에서 접근하게 되는 경우 loop 한 번마다 쿼리가 한 번씩 발생함으로써 엄청난 DB 연결 비용이 발생하는 부분을, 최초 list를 가져올 때 함께 가져올 수 있도록 개선했다.
  • 미리 계산할 수 있는 값은 미리 계산해두기: 한 채권에 법인과 기관이 투자한 비중을 표시하기 위해 채권목록을 각각 순회하며 계산하는 대신, 투자행위가 일어날 때마다 DB에 미리 계산된 값을 넣어두고 이를 활용했다.
  • DB Indexing: Index를 설정해주어서 해결할 수 있는 문제들은 해당 컬럼에 Index를 잡아주었다.
  • Lock과 Transaction을 정확히 필요한 곳에서만 사용하기: 다수가 하나의 채권에 동시에 투자를 요청하기 때문에 Lock과 Transaction 처리는 필수이다. 그러나 투자하기 API에서 처리하는 모든 내용을 한 Transaction 내에서 작업하다보니 현재 투자가 진행되는 사용자 외에 다른 사용자들은 대기시간이 길어지는 문제가 있었다. 이를 반드시 필요한 영역만 Lock과 Transaction을 사용함으로써 한 사용자가 Lock을 거는 시간을 최소화했다.

Django로 제작한 웹앱내에서 API 성능개선을 할 때 참고할 수 있는 부분들을 Django의 공식 가이드를 기반으로 Django DB 최적화 포스트에 상세히 정리하였다. ORM이 개발자에게 주는 혜택은 한 마디로 설명할 수 없을 정도로 많지만, 반면 실제로 DB와 주고받는 쿼리를 상세히 제어하는 것은 더 힘들어진 것이 사실이다. Django의 ORM에 대해서 좀 더 상세히 공부하는 기회가 되었다.

더불어서 추후 투자하기 API를 클라이언트와 서버간 통신을 비동기로 변경하고 Queue에 넣어서 처리한 뒤 클라이언트는 결과값을 polling하는 방식으로 변경하는 작업을 진행할 예정이다. 단일 채권에 대다수의 투자자가 동시에 접근하여 투자를 신청하는 과정을 Lock을 걸면서 동기로 처리하는 방식은, 추후 사용자가 더 많아졌을 때 다시 문제를 야기시킬 것으로 예상된다.

Caching

몇몇 조회용 API는 완전히 실시간으로 DB와 동기화되어야 하는 것은 아니었다. 이에 API 자체를 CloudFront를 통해 캐싱을 적용했다. 그러나 피크 타임에 몰리는 트래픽으로 인해 변경되는 값을 빠르게 갱신해줄 필요성이 있었다. 따라서 시간에 따라 값이 변경되는 부분에 한해서는 1분 동안만 캐싱이 되도록 설정하고, 파라미터에 따라 값이 고정되는 부분은 30분 이상 캐싱이 되도록 설정해두어서 프론트엔드에서는 캐싱된 API를 사용하도록 했다.

소켓 연결 제한

이 문제가 원인을 파악하는데 시간을 가장 많이 소요하게 만들었던 부분이었다. Nginx에서 웹백엔드 애플리케이션까지 도달하지 못하고 바로 에러페이지를 반환하는 문제가 있었다. 이 때문에 JMeter를 활용해 개발용 서버에 부하를 주면서 원인을 파악해보았다. 최초 PHP-FPM이나 uWSGI의 설정에 문제가 있을 것으로 판단하고 프로세스의 갯수 등을 조절하는 방향으로 접근했지만 원인을 찾을 수 없었다. 결국 밝혀낸 원인은 프로세스가 소켓에 연결을 시도할 때 한도를 넘어섰다는 것이었다. Nginx 로그에 남아있는 다음과 같은 에러가 힌트를 주었다.

... connect() to unix:...sock failed (Resource temporarily unavailable) ...

몇몇 문서를 찾아본 결과 별도로 설정하지 않은 경우에는 하나의 소켓이 받아들일 수 있는 연결의 갯수는 128개인 것 같았고, 피크 타임에 Nginx의 한 프로세스가 처리하는 요청이 많아지다보니 128개 이상의 요청을 동시에 처리하려고 시도하다가 소켓에 접근을 못하게 되었던 것으로 추정되었다. 이는 /etc/sysctl.conf 파일에 net.core.somaxconn=1024과 같이 저장하여 영구 적용을 할 수도 있고, sudo /sbin/sysctl -w net.core.somaxconn=1024과 같은 명령어로 즉시 적용할 수도 있다. (후자는 서버가 종료되었다가 다시 시작되는 경우 설정이 초기화된다.) 또한, 설정을 변경하더라도 애플리케이션이 재시작되어야 적용된다. 우선 net.core.somaxconn 파라미터를 늘려서 당장의 문제를 해결할 수 있었다. 리눅스의 소켓이 받아들일 수 있는 연결의 제한에 대해서 정확히 이해하기 위해서는 조금 더 심도있는 글들을 참고하는 것이 좋다.1 2 3

이와 더불어 일부 사용자가 Nginx의 기본 에러페이지를 보게되는 경우에도 너무 당황하지 않을 수 있도록 이쁜(?) 페이지를 제작하여 넣어두었다. Nginx의 기본 에러페이지를 변경하는 방법은 여러 글들에서 많이 소개되어 있다.4

AWS DynamoDB Units

피플펀드 백엔드 서비스에서는 AWS DynamoDB를 여러 서버간 세션을 공유하는 DB로 활용하고 있다. DynamoDB와 관련된 문제는 Production 환경에서 발생하지는 않았지만, JMeter로 개발용 서버에서 테스트를 진행하던 중, DynamoDB에 접근을 못한다는 에러를 보게되었다. 이는 DynamoDB의 ‘Unit’이라고 불리는 처리량 설정과 관련된 문제이다. 한창 피크타임에 홈페이지가 제대로 동작하지 못하는 문제를 겪을 당시에는 앞서 언급한 소켓연결 갯수제한이 문제였기 때문에 DynamoDB로 연결조차 시도해보지 못하고 Nginx에서 즉시 오류가 반환되었었다. 따라서 정확한 설정값을 찾기는 어려웠지만, CloudWatch를 통해 대략적으로 산정해본 request 수보다 넉넉하게 처리량을 변경해두었다. DynamoDB의 처리량에 대한 설정값은 AWS의 공식 문서를 참조해야 한다.

AWS EC2 Instance Specs

앞서 언급한 바와 같이 EC2 인스턴스가 스왑메모리를 너무 많이 사용하는 문제가 있었는데, 이건 현재로서는 완벽하게 처리하기 어려운 상황이다. 하루에 단 10~20여분 발생하는 순간트래픽 때문에 좋은 성능의 인스턴스를 사용하기에는 너무 리소스가 아까워서 상당히 낮은 스펙의 인스턴스를 띄워둔 상황이었다. 그러나 순식간에 인스턴스를 더 띄울 수 있도록 완전한 배포 자동화가 이루어지지 않은 상태였다. 또한 완전한 배포 자동화에는 시간이 너무 많이 필요하여 당장 작업을 할 수는 없었다. 결국 인스턴스의 스펙을 좀 더 올리고 여분의 인스턴스를 미리 생성해둔 뒤, 필요한 경우(상품 오픈을 알리는 문자 및 알림톡이 발송된 직후에 트래픽이 몰리므로 미리 예상이 가능하다)에만 스크립트를 통해 여분의 인스턴스들을 띄워서 ELB에 연결하여 사용하다가 트래픽이 감소하면 다시 중지시켜두는 방향으로 타협했다.

이 부분은 완전한 배포 자동화를 지속적으로 준비하여 조금 더 빠르고 쉽게 대처할 수 있도록 개선이 필요할 것이다. 다만, 피플펀드 서비스의 특성은 AWS의 Auto-scaling과 맞지 않아 조금 다른 방법을 구상해야 한다. 현재 운영에 필요한 수많은 작업들을 Slack을 통해 명령을 내려서 처리하도록 자동화를 해두었는데, EC2 인스턴스를 준비하는 과정 또한 최종적으로는 Slack에 명령어를 하나 추가해두어서 외부에서도 재빠르게 대응할 수 있도록 준비하는 방향성을 구상중이다.

성과 측정

성능개선 작업을 하게되면 필연적으로 따라오는 것이 성과의 측정이다. 그러나 이번 프로젝트는 당장 사용자들이 오류페이지를 보거나 매우 느린 속도를 경험하게 되는 문제를 빠른 시간 내에 해소하는 것이 목표였기 때문에 정밀한 성과측정을 하지는 않았다. 따라서 트래픽 대비 오류반환 비율이 개선된 점에 대해서만 다른 부서와 공유를 하고 마무리하게 되었다.

눈으로 볼 수 있는 성과

주요 페이지들을 브라우저에서 띄워보면, 평소에는 거의 로딩시간 없이 렌더링되었고 피크 타임에도 약간의 느려짐은 경험할 수 있었지만 대체적으로 부드럽게 렌더링되는 것을 확인할 수 있었다. 또한 Nginx의 에러페이지는 볼 수 없었다.

AWS CloudWatch

개발팀 내에서만 공유되었지만, CloudWatch에 설정해둔 대다수의 지표에서 성과가 나타나게 되었다. 해소하지 못한 주요지표로써 RDS의 CPU 사용률이 남아있는 상태이지만, 이번 프로젝트의 후속으로 API 개선작업을 좀 더 진행할 예정이라 더 개선될 것으로 기대하고 있다. 가장 중요한 지표로 보았던 피크 타임의 ELB 오류반환 비율은 개선 전 25% 정도 수준에서 개선 후 3% 이하 수준으로 줄일 수 있었다. 그러나 사용자들로부터 잘못된 요청이 이루어지는 경우에도 역시 오류를 반환하기 때문에 완전히 없애는 것은 불가능할 것이다.

New Relic APM

Top 5 API를 모두 ‘Most time consuming’ 탭에서 완전히 밀어내는데 성공했다. 그러나 그 아래 있던 새로운 API들이 다시 Top 5를 꿰차고 앉았고, 이는 추후 지속적인 개선을 진행해야 할 것이다.

소고

사실 스타트업에게 있어서 트래픽으로 인한 서버다운은 반길만한 상황이다. 예상보다 더 많은 사용자들이 서비스를 이용한다는 의미이기 때문이다. 하지만 개발자의 입장에서는 기뻐할 수만은 없는 노릇이다. 더군다나 피플펀드는 금융서비스이다보니 더욱 안정성에 신경을 기울일 수 밖에 없다. 그러나 적은 수의 인원으로 사업의 성장을 견인하기 위해서는 앞을 보고 달려가야하니 뒤를 돌아보기 힘든 것이 현실이다. 그럼에도 불구하고 이번 사건은 조금씩 뒤를 돌아보고 안정적인 서비스를 만들기 위한 노력을 기울여야 한다는 점에 대해 공감대를 형성하는데 기여할 수 있었다.

References