# SDK 대본
https://www.notion.so/SDK-f497f44a1edd402e8b77909b939fc360
1. 이 고민이 나오게 된게 경험을 바탕으로 나왔다고 할 것인가
2. 전략 패턴을 얘기할 때 왜 전략패턴을 하게 됐는지 ?! 에 대한 내용을 넣을 것인가 .
왜 전략패턴?!
전략패턴은 동적으로 알고리즘을 교체할 수 있는 구조로 최종 목적은 동일한데 그 방법이 여러가지 존재할 때 사용한다고 합니다. 플랫폼이 다양할 수 있다는 확장성을 고려했을 때, 전략패턴으로 각 플랫폼에 알맞는 알고리즘을 구현해서 갈아끼울 수 있도록 구현하기에 적합한 패턴이라고 생각됩니다.
함수형 ??! :
Sentry 파일 import 하면서 확장
공통 : base
browser, node -> captureError / window navigator
갖고올 수 있는 정보가 // 다름 다 수집하고 공통된걸로 넘겨서 ~~
프로젝트를 진행하며 잡았던 목표 1)다양한 플랫폼에서 2)안정적으로 에러 모니터링을 해준다.
다양한 플랫폼을 지원하는 SDK가 필요했고
쏟아지는 에러 속에서 버틸 수 있는 서버가 필요했다.
=====
대충 850자 = 3분
=====
영상 데모
먼저 Express 프로젝트 생성입니다. 보시는 바와 같이 관리자 멤버 설정과 메일설정이 가능합니다.
프로젝트 생성완료후 사용법을 그대로 쓰시면 됩니다. NPM 설치후 import 하시고 init를 설정합니다. init 옵션은 dotenv를 사용하시는 것을 추천합니다.
다음으로 에러를 받았을 경우 화면입니다. 에러 코드를 볼 수 있으며, 통계또한 나타납니다. 댓글또한 작성가능합니다.
프로젝트 멤버 추가도넣을까
다양한 플랫폼으로 수평 확장될 시 저희가 생각한 발생할 수 있는 문제점은 다음과 같았습니다.
1. 기능이 수정, 추가될 때 각 플랫폼의 코드들을 각각 고쳐야 할 수 있다.
추후 코드가 더 확장된다면 이 부분은 치명적인 문제점이 될 수 있다고 생각하였습니다. 그렇기 때문에 저희는 같은 알고리즘으로 동작할 수 있는 기능은 공유하여 중복이 없도록 하고, 따로 구현이 필요한 기능은 최소한의 코드 추가로 효율을 높이고 싶었습니다.
이를 위해 저희는 전략 패턴으로 전체 구조를 잡았습니다.
같은 코드로 동작할 수 있는 기능은 부모 클래스를 만들어 모든 플랫폼에 공통되는 기능을 구현하였습니다. 각 플랫폼은 이 부모 클래스를 상속받아 플랫폼에 따라 다르게 구현이 필요한 기능을 추가하여 하나의 전략 클래스로 만들었습니다.
그 뒤 어떤 플랫폼을 사용하느냐에 따라 전략 클래스를 다르게 설정할 수 있도록 하였습니다.
그 결과 새로운 기능을 추가하거나 새로운 플랫폼을 추가할 때 추가해야 하는 코드가 적어 편했고, 플랫폼에 의존적인 부분을 분리했기 때문에 공통된 코드는 부모 클래스에서만, 플랫폼에 의존적인 코드는 각 전략 class에서만 수정하면 되어 효율적인 작업이 가능했습니다.
다양한 플랫폼의 수평 확장에 있어 발생할 수 있는 두 번째 고려했던 점은
2. 플랫폼별로 의존성 관리를 다르게 해줄 필요가 있다
Santry 는 React, Vue 등 여러 프레임워크로 확장될 수 있어야 하기에
모든 코드를 한 패키지로 관리한다면 하나의 프레임워크를 쓰는 사용자도 모든 프레임워크 패키지를 설치해야 한다는 문제가 있었습니다.
그래서 플랫폼별로 패키지를 나누어야 할 필요성을 느꼈고
multi-repo, mono-repo 두 가지의 선택지가 있었습니다.
저희는
1. 공통된 코드를 편하게 사용하고 싶다.
2. 모든 패키지가 공통 설정을 공유하고 싶다.
3. 패키지들의 test, build, publish 등을 한 번에 쉽게 하고 싶다.
라는 점 때문에 mono-repo로 선택하게 되었습니다
그래서 저희는 yarn workspace와 lerna를 써서 레포지터리를 관리했고, 그 결과 편하게 sdk를 개발할 수 있었습니다.
두번째로 저희는 많은 양의 에러를 핸들링 할 수 있는 서버를 만들고 싶었습니다.
~~ 성능 테스트가 필요했고, 저희는 아티렐리라는 간단한 툴을 이용하여 진행하였습니다.
그 결과 ㅇㅇㅇ 한 결과를 얻었다
동시성 이슈를 해결해야 했다
---
다음으로, 저희는 SDK에서 오는 많은 양의 에러와 로그를 핸들링해야했기 때문에 서버의 성능을 더 신경써야 했습니다.
특히 에러를 잡는 핵심 로직의 성능이 좋아야 한다고 생각하여 부하테스트 툴인 아티렐리를 활용하여 직접 성능을 수치화 해 보기로 결정하였습니다.
저희는 서버에서 에러를 수집하는 api인 captureError api 위주로 성능측정을 진행하였습니다.
먼저 저희는 pm2 클러스터링을 적용하였을 때와 적용하지 않았을 때를 실험하였습니다.
()
( 프로세스 4개 등 실험 조건 적으셈 )
( 조건 - 모두 동일, core 4개 )
[비교 사진 넣읫에ㅕ ]
그렇게 계속 개발를 진행하다 저희는 예상치 못했던 이슈를 만나게 되었습니다.
저희의 서비스에서 같은 Error들을 모아 종합통계를 볼 수 있는 Issue 기능이 있습니다.
저희는 Error가 서버에 도착했을 때 기존에 있던 Error라면 기존의 Issue에 추가하고, 새로운 종류의 Error 라면 새로운 Issue를 생성하여 추가하도록 하였습니다.
하지만 이런 방식으로 처리하게 된다면,
여러 개의 프로세스에 동시에 새로운 종류의 Error가 들어왔을 때 프로세스 갯수만큼의 새로운 Issue가 생성되어, 동시로 발생한 Error의 갯수 -1 만큼의 미아 Issue와 Error가 발생합니다.
유실? 새로운 이슈가 mongoose의 (eventName, errorMessage, errorStack, issueType, projectId)
동시성에 관한 저희의 설계 문제였습니다.
/* 우리 설계 => issue : error = 1 : n
해결 => issue : error = 1 : 1 */
저희의 설계는 이슈 하나에 error N개가 묶여있는 1대 다 관계였습니다.
해결 : issue 하나당 error하나로 묶어서 모델링하고 보여줄 때는 쿼리로 그룹핑 하도록 설계했어야 했습니다.
하지만 남은 시간이 많지 않았고, 저희는 기존의 모델에서 다른 방법으로 해결할 수 있을지 고민했습니다.
저희는 Error의 비교 대상이 되는 속성들을 컴파운드 인덱스로 묶고 유니크 옵션을 주었습니다.
따라서 여러개의 프로세스가 동시에 같은 Error를 다른 이슈에 저장하려고 시도 하는 경우 unique 속성에 관한 에러가 발생합니다.
unique 속성으로 인한 오류일 경우 다시 findOne으로 이슈를 찾아 에러를 넣을 수 있도록 하였습니다.
 (PPT)
그 과정에서 설정한 컴파운드 인덱스 덕분에 findIssue 로직의 성능 향상되어 결과적으로 captureError api의 성능이 향상되는 결과를 얻을 수 있었습니다.
[비교사진 넣으세여 ]넹

이슈를 찾는거에서 빨라짐
이벤트를 보냄 -> (여기서 빨라짐) 해당 이슈가 존재하는 지 찾음 -> 없으면 만듦 -> 있으면 거따 넣음 -> 이벤트 저장
/* captureEvent */ api issueFind - api issue 넣음 - 201 뜨나 ?
먼저
* 아티렐리 중심 성능측정 -> 핵심 로직의 성능이 좋ㅇ야함 -> 성능츠정 수치화ㅏ -> 아테렐릿 ㅏ용 -> 간단하게 클러스터링 전과 후 비교 하면서 ㅅ ㅣ작 ㅋ => 핵심로직 오류 -> ~게 해결( index 넣음 ) -> 아티렐리 성능 측정결과로 끝 */
~~다음자료는 아티랠리 테스트 결과 입니다. 먼저 보여줄 자료는 클러스터링 적용 유무 차이를 말씀드리려 합니다. 싱글프로세스로 운영되는 서버의 응답 속도를 보면 다음과 같습니다. 여러개의 트래픽을 동시에 보낼때 심한경우 10초까지 응답이 늦어지는 것을 볼 수 있습니다. 성능 개선을 위해 이 후 클러스터링을 적용한 결과 입니다. 저희가 예상한 결과보다 훨씬 더 좋은 결과가 나온 것을 볼 수 있습니다. 하지만 클러스터링을 함으로 다음과 같은 문제가 생겨났습니다~~
( 다음 자료는 1초에 1000개의 Error 를 10초 동안 보내는 API 의 결과 비교분 입니다. )
[자료]
~~4개의 프로세스를 고려하지 못하고 설계하여서 문제가 생겼습니다.~~
저희의 서비스에서 같은 Error들을 모아 종합통계를 볼 수 있는 Issue 기능이 있습니다. 저희는 이 기능을 구현하기 위해 Error가 서버에 도착했을 때 기존에 있던 Error 라면 기존의 Issue에 추가하고, 새로운 종류의 Error 라면 새로운 Issue를 생성하여 추가하도록 하였습니다.
하지만 이런 방식으로 처리하게 된다면, 4개의 Node 프로세스에 동시 새로운 종류의 Error 가 들어왔을 때 4개의 새로운 Issue 가 생성되는 문제점이 있었습니다.
우리는 이를 해결하기 위해서 여러가지를 고민해 보았다.
첫번째 방법으로 Transaction입니다. 하지만 이 방법은 Error 를 받는 부분에서 명시적으로 Lock 을걸어 매우 느리게 만들어서, 저희가 처음 목표로 했던 성능을 좋게 하려는 것과 상반됩니다. 또한 NoSQL의 경우 트랜스액션 미사용을 지향하기에 다른 방법을 고민하기로 했습니다.
두번째 방법으로 multi index 설정입니다. mongoose의 Error의 비교 대상이 되는 속성들을 컴파운드 인덱스로 묶고 유니크 옵션을 주었습니다. 따라서 4개의 Node 프로세스가 동시에 Error가 같은 이슈를 저장하려고 시도 하는 경우 unique 속성에 관련되서 에러가 발생합니다. 같은 issue는 저장할 수 없기 때문이죠. 그 후 try catch로 한번 잡고 unique 때문에 생긴 오류일 경우 다시 findOne으로 이슈를 찾습니다. 또한 인덱스 설정으로 검색 속도까지 높이니 일석이조의 효과를 얻습니다.
.........
===
core/ node/ browser/ 레포 분리!!
그리고
전략패턴을 활용해서 플랫폼을 추가하던 중, 또 다른 고민이 생겨버려따
만약 react 플랫폼을 만들게되면 react를 의존성에 넣어야 하는데 그렇다면 다른 플랫폼 사용자에게 불필요한 의존성이 추가되어버리넹
플랫폼 별로 패키지를 나누어야 할 필요가 있었당 근데 선택지는 2개~~ 우리는 mono-repo 선택
모노레포 방식으로 패키지를 관리하였습니다.
=> mono-repo
각 플랫폼별로 의존성 정보가 달랐기 때문에 패키지를 분리해야 할 필요가 있었습니다.
mono-repo 를 도입함으로써 저희는 공통 코드, 공통 설정을 사용할 수 있었고 또 workflow 를 최적화할 수 있었기 때문에 편하게 sdk 를 개발할 수 있었습니다.
Lerna 설정
저희 SDK의 가장 큰 특징은 다양한 플랫폼으로 수평확장될 수 있다는 것이라고 생각했습니다.
다양한 플랫폼으로 수평확장될 시 저희가 생각한 발생할 수 있는 문제점은 다음과 같았습니다.
1. 기능이 수정, 추가될 때 모든 플랫폼의 코드를 전부 고쳐야할 수 있다.
물론 지금은 플랫폼이 2개여서 괜찮지만, 만약 코드가 더 커진다면 이 부분은 치명적인 문제점이 될 수도 있ㄷ고 생각하였습니다.
그래서 저희는 전략패턴을 도입하였습니다.
우선, 모든 플랫폼을 아우르는 base Class 를 두어 모든 플랫폼에 공통되는 기능을 구현하였습니다. 그리고, 그 클래스를 상속하는 node, browser Class 를 만들어 기능은 같지만 알고리즘이 서로 다른 함수들을 구현하였습니다 .
그 뒤로 공통된 코드는 baseSantry 에서만, 공통되지 않은 코드는 각 전략 class 에서만 수정할 수 있었습니다.
그리고 저희는 mono-repo 를 도입하였습니다.
mono-repo 를 도입함으로써 저희는 공통 코드, 공통 설정을 사용할 수 있었고 또 workflow 를 최적화할 수 있었기 때문에 편하게 sdk 를 개발할 수 있었습니다.
다양한 플랫폼을 지원하는 SDK를 구현함에 있어서 메소드를 크게 두가지로 구분할 수 있었습니다.
첫 번째는 모든 플랫폼에서 같은 코드로 동작할 수 있는 기능, 두 번째는 플랫폼별로 따로 구현해야하는 기능입니다.
다양한 플랫폼에서 안정적인 서비스를 위한 노력
에러 수집 서비스의 확장성과 안정성을 위한 우리의 고민
핵심로직은 중요하기 때문에 , 수치 화할 필요가 있었고
Artillery 로 테스트를 해봤엇고
클러스터링 전과 후 를 테스트 해봤었다..
핵심로직이 오류가 있다는 것을 발견했다.
comp
처리량이 많은 콜렉션에 대해 도큐먼트 insert 시 의존이 생기는 설계는 잘못된 설계 이슈와 이벤트를 1:1로 레퍼런싱하는 모델이거나 이슈와 이벤트를 분리하지 않고 합치거나 임베딩 하는 모델로