# 대본 최종
2페이지
안녕하세요 11프로젝트 A 조 발표를 맡게 된 김은빈입니다.
저희는 에러 수집 서비스의 확장성과 안정성을 위한 우리의 고민 이라는 주제로 발표를 진행하도록 하겠습니다.
3페이지
우선 저희 프로젝트는 어플리케이션에서 발생할 수 있는 에러와 로그를 모니터링을 할 수 있는 플랫폼입니다.
4페이지 (2:00)
먼저 짧은 데모영상으로 소개드리겠습니다.
저희 서비스에 가입하시면, Express, Node, Javascript 플랫폼의 프로젝트를 생성하실 수 있습니다. 보시는 바와 같이 프로젝트를 같이 공유할 수 있도록 멤버설정이 가능하구요, 일정 에러 레벨 이상의 에러 발생 시 알람을 받아볼 수 있는 메일 설정이 가능합니다.
프로젝트 생성을 완료하면 어떻게 SDK를 설치하고 사용하면 되는지 안내가 나옵니다.
이 안내에 따라 이렇게 npm으로 패키지를 설치하시고, 프로젝트에 import 및 init을 추가하시면 기본 세팅이 완료됩니다.
이제 이 프로젝트에서 발생하는 에러들은 이 홈페이지에서 확인하실 수 있습니다.
에러가 방금 들어온 것 같습니다, 이슈를 클릭해보면 에러의 스택트레이스, 또 os, browser 정보와 같은 메타 정보들, 개발자 분께서 심어둔 context 정보를 확인하실 수 있습니다.
메타 정보들은 통계 그래프로 한눈에 확인하실 수 있구요, event들은 리스트로도 확인할 수 있으며 검색도 가능합니다. 댓글 탭에서는 자유롭게 팀원분들과 이슈에 대해 이야기 나눠보실 수 있도록 되어있습니다.
5페이지
Santry 는 다음과 같은 기술 스택으로 되어 있습니다. SDK는 러나,얀으로 관리하고 , 서버는 Express 로 만든 node를 피엠2 로 관리하고 데이터베이스는 몽고디비를 사용하고 있습니다. 프론트 부분에서는 React ,머터리얼 유아이,바벨,웹팩,Nginx 를 사용했습니다.
6페이지
Santry 를 만들며 저희가 잡았던 목표는 , 다양한 플랫폼에서 안정적으로 에러를 모니터링 하는 것이었습니다.
7페이지
이 목표를 위해서는 다양한 플랫폼을 지원하는 SDK 가 필요했고 , 쏟아지는 에러를 핸들링 할 수 있는 서버가 필요했습니다.
8페이지
SDK 에서 다양한 플랫폼으로 수평 확장하며 저희는 두 가지 고민이 있었습니다. 첫번째로는
9페이지
기능이 수정, 추가될 때 각 플랫폼의 코드들을 각각 고지치않고 효율적으로 개발을 하기 좋을까 입니다.
저희는 같은 알고리즘으로 동작할 수 있는 기능은 공유하여 중복이 없도록 하고, 따로 구현이 필요한 기능은 최소한의 코드 추가로 효율을 높이고 싶었습니다.
이를 위해 저희는 전략 패턴으로 전체 구조를 잡았습니다.
10페이지
같은 코드로 동작할 수 있는 기능은 부모 클래스를 만들어 모든 플랫폼에 공통되는 기능을 구현합니다.
11페이지
그리고 이 부모 클래스를 상속받아 플랫폼에 따라 다르게 구현해야하는 기능을 추가하여 하나의 전략 클래스로 만들었습니다.
12페이지
이렇게 만든 전략 클래스는 사용자가 이용하는 플랫폼에 따라 갈아끼울 수 있습니다.
13페이지
그 결과 새로운 기능이나 플랫폼을 추가할 때 구현해야 하는 코드가 적어 편했고, 공통된 코드는 부모 클래스에서만, 플랫폼에 의존적인 코드는 각 전략 class에서만 수정하면 되어 효율적인 작업이 가능했습니다.
14페이지
저희의 두 번째 고민은 플랫폼 별로 각각 다른 의존성들을 어떻게 편하게 관리 할 수 있을까 였습니다.
Santry 는 React, Vue 등 여러 플랫폼으로 확장될 수 있습니다.
만약 모든 코드를 하나의 패키지로 관리한다면 모든 플랫폼의 의존성이 합쳐져버리는 문제가 발생할 수 있습니다.
15페이지
그래서 저희는 플랫폼별로 패키지를 나누어야 할 필요성을 느꼈고
multi-repo, mono-repo 두 가지의 선택지가 있었습니다.
16페이지
저희는 공통 코드를 편하게 사용하고 싶고, 모든 패키지의 공통된 설정을 공유하고 싶었습니다. 또, 패키지들의 test, build, publish 등을 한 번에 쉽게 하고 싶었기 때문에 mono-repo로 선택하여 개발하게 되었습니다.
17페이지
그래서 저희는 yarn workspace와 lerna를 써서 레포지터리를 관리했습니다. 덕분에 한결 편하게 sdk를 개발할 수 있었습니다.
18페이지
두 번째 목표인 쏟아지는 에러를 핸들링 할 수 있는 서버를 만들기 위해서 저희는 서버의 성능에 조금 더 신경 썼습니다.
특히 에러를 잡는 핵심 로직의 성능이 좋아야 한다고 생각해
19 페이지
아티렐리를 활용한 스트레스 테스트로 직접 성능을 측정해 보았습니다.
간단하게 아티렐리 사용을 확인해보기 위해 pm2 클러스터링 적용 전후를 비교해 보았고 확실히 응답속도가 빨라짐을 수치적으로 확인해볼 수 있었습니다.
스트레스 테스트를 해보던 중 저희는 예상치 못한 문제점을 발견하였습니다.
20페이지
저희의 서비스에서는 같은 종류의 에러를 모아 하나의 이슈로 관리하는데요, 이를 위해 새로운 에러가 도착하면 이미 이슈가 생성되어 있는 에러인지 확인한 뒤, 있다면 기존의 issue에 에러를 추가하고 없다면 새로운 이슈를 생성합니다.
21페이지
저희는 pm2 클러스터링으로 4개의 프로세스를 사용하고 있었기 때문에, 하나의 DB에 여러개의 프로세스가 동시에 접근하였고 이슈가 생성되지 않은 시점에 들어온 동일한 이벤트들은 각각을 새로운 이슈로 만들어버렸습니다.
22페이지
이는 곧 사용되지 않는 이슈이기에 유실됐다고 볼 수 있습니다.
23페이지
처리량이 많은 콜렉션에서 도큐먼트 insert시 의존이 생긴다는 설계에서부터 잘못된 문제였습니다.
관계형 데이터베이스에 익숙해져버려 MongoDB를 관계형 모델로 잘못 접근했던것 같습니다.
NoSQL의 특징을 정확히 파악하여 issue와 error 컬렉션을 분리하지 않고 하나로 합쳐 저장했다면 생기지 않았을 문제였습니다.
24페이지
하지만 이 문제를 발견했을 즈음엔 이미 많이 늦어버렸고 저희는 기존의 모델에서 다른 방법으로 해결할 수 있을지 고민했습니다.
25페이지
저희는 몽고디비의 유니크 컴파운드 인덱스를 통한 무결성 제약조건을 걸어 문제를 해결했습니다.
이에 따라 여러개의 프로세스가 동시에 같은 에러를 다른 이슈로 저장하려 시도하는 경우 무결성 제약 조건에 위배되어 에러가 발생합니다.
이 에러를 catch 하여 다시 findOne으로 이슈를 찾고 에러를 넣을 수 있도록 함으로써 해결할 수 있었습니다.
26페이지
그 과정에서 설정한 인덱스 덕분에 findIssue 로직의 성능이 향상되어 결과적으로 captureError api의 성능이 향상되는 결과까지 얻을 수 있었습니다.
27페이지
저희의 발표는 여기까지입니다. 이외에도 저희가 프로젝트를 진행하며 다양한 도전과 고민을 했었는데요, 혹시 궁금하시다면! 저희 부스를 방문해주시면 감사하겠습니다. Q&A 시간을 갖도록 하겠습니다.
Q&A 후
감사합니다. 이것으로 발표 마치도록 하겠습니다.
=======
그 뒤 어떤 플랫폼을 사용하느냐에 따라 전략 클래스를 다르게 설정할 수 있도록 하였습니다.
하나의 프레임워크를 쓰는 사용자도 모든 프레임워크 패키지를 설치해야 한다는 문제가 있었습니다.
저희는 REST API를 사용하였고 그 중 서버에서 에러를 수집하는 api 를 타겟으로 하여 성능측정을 진행하였습니다.
다음은 클러스터링을 적용하였을 때와 적용하지 않았을 때를 실험하던 결과입니다. 보시는 바와 같이 응답 시간 단축을 확인 할 수 있습니다. 하지만 클러스터링을 통해서 예상치 못한 이슈를 접하게 되었습니다.
(결과 값에 대한 언급도 해야 될 것 같음)
원본: 저희는 서버에서 에러를 수집하는 api인 captureError api 위주로 성능측정을 진행하였습니다.
다음은 클러스터링을 적용하였을 때와 적용하지 않았을 때를 실험하던 결과입니다.
저희의 서비스에서 같은 종류의 Error들을 모아 종합통계를 볼 수 있는 Issue 기능이 있습니다. 저희는 Error가 서버에 도착했을 때 기존에 있던 Error라면 기존의 Issue에 추가하고, 새로운 종류의 Error 라면 새로운 Issue를 생성하여 추가하도록 하였습니다. 문제는 여기서 발생합니다.
원본:
그러면서 계속 개발를 진행하다 저희는 예상치 못했던 이슈를 만나게 되었습니다.
저희의 서비스에서 같은 Error들을 모아 종합통계를 볼 수 있는 Issue 기능이 있습니다.
저희는 Error가 서버에 도착했을 때 기존에 있던 Error라면 기존의 Issue에 추가하고, 새로운 종류의 Error 라면 새로운 Issue를 생성하여 추가하도록 하였습니다.
21페이지
여러 개의 프로세스에 동시에 새로운 종류의 Error가 들어왔을 때 프로세스 갯수만큼의 새로운 이슈가 생성합니다. 원래 싱글쓰레드 처리라면 하나의 이슈가 저장된후 다음 이벤트를 받지만, 멀티쓰레딩으로 인해 성능을 얻은 댓가를 치룬 셈 입니다.
원본:
하지만 이런 방식으로 처리하게 된다면,
여러 개의 프로세스에 동시에 새로운 종류의 Error가 들어왔을 때 프로세스 갯수만큼의 새로운 Issue가 생성되어,
22페이지
따라서 같은 종류의 Issue가 네 개 발생하고, 이후 같은 Error에 대해서는 맨 처음 만들어진 이슈에만 할당되게 됩니다. 즉 나머지 Issue와 그 안에 포함된 Event는 미아 프로세스와 같은 처지가 됩니다.
원본:
처음 이슈를 제외하고 생긴 이슈 만큼의 미아 Issue와 Error가 발생합니다.
23페이지
동시성에 관한 저희의 설계 문제였습니다. 관계형 데이터베이스에 익숙해져 버려 mongoDB를 관계형 모델로 설계했다가 발견된 문제점이 되버렸습니다. 다시 생각해보면 처음 설계할때 NoSQL의 특징을 정확히 파악하고 issue와 error 컬렉션을 분리하지 않고 하나로 합쳐 저장한다면 생기지 않는 문제였습니다. 하지만 남은 시간이 많지 않았고, 저희는 기존의 모델에서 다른 방법으로 해결할 수 있을지 고민했습니다.
원본:
동시성에 관한 저희의 설계 문제였습니다.
저희의 설계는 이슈 하나에 error N개가 묶여있는 1대 다 관계였습니다.
다시 생각해보면 issue 하나당 error하나로 묶어서 모델링하고 보여줄 때는 쿼리로 그룹핑 하도록 설계했어야 했습니다.
하지만 남은 시간이 많지 않았고, 저희는 기존의 모델에서 다른 방법으로 해결할 수 있을지 고민했습니다.
저희는 Error의 비교 대상을 몽고디비의 컴파운드 인덱스, 유니크 인덱스를 활용해 무결성 제약 조건을 걸었고 이에 따라서
여러개의 프로세스가 동시에 같은 Error를 다른 이슈에 저장하려고 시도 하는 경우 unique 속성에 관한 에러가 발생합니다.
unique 속성으로 인한 오류일 경우 다시 findOne으로 이슈를 찾아 에러를 넣을 수 있도록 하였습니다.
저희 프로젝트 사용법을 알려드리겠습니다. 가입후 먼저 Express 프로젝트 생성입니다. 보시는 바와 같이 관리자 멤버 설정과 메일설정이 가능합니다.
프로젝트 생성완료후 사용법을 그대로 쓰시면 됩니다. NPM 설치후 import 하시고 init를 설정합니다. init 옵션은 dotenv를 사용하시는 것을 추천합니다. 다음으로 에러를 받았을 경우 화면입니다. 에러 코드를 볼 수 있으며, 통계또한 나타납니다. 특정 이슈에 댓글또한 작성가능합니다.
짤 소집중

13