-
Notifications
You must be signed in to change notification settings - Fork 3
3. 라이브러리 선택
목차 원본 : https://github.com/cupicks/.github/tree/main/profile/libs
프로젝트 팀 결성 이후, @unchaptered
와 @axisotherwise
는 프로젝트 초기 설정, Lib 선택, 으로 많은 이야기를 나누었습니다.
서로 원하는 기술 스텍 이나 흥미를 느껴하는 부분이 너무나 달랐기 때문에 협의 과정이 어려웠습니다.
따라서, 서로가 모두 동감하는 아이덴티티 (지향점) 을 고르고 이를 원칙으로 삼자고 정했습니다.
저희 둘의 목표는 안정적인 개발
이었으며, 나아가서 cupick 의 지향점도 동일했습니다.
이에 따라 주제 선정 이후 다음과 같은 의사결정을 하게 되었습니다.
- 타입스크립트
- MySQL 선택
- Raw Query 선택
- AWS Lambda 이미지 리사이징
- JWT RS256 선택
우리의 아이덴티티는 안정적인 개발
입니다.
따라서 일회성 서버가 아니라, 안전하고 지속적으로 관리할 수 있는 서버를 만들고 싶었습니다.
- 3.3.1. ORM 에서 Raw Query 까지
- 3.3.2. Raw Query 에서 느낀 장점 1 - 성능
- 3.3.3. Raw Query 에서 느낀 장점 2 - 실행 계획
- 3.3.4. Raw Query vs Query Builder vs ORM
안전한 서버란, 배포 전에 최대한 많은 에러를 잡아낼 수 있어야 한다고 생각합니다.
하지만 JS 를 통해서 개발을 해보면 다음과 같은 불편함을 느끼게 됩니다.
const sayHello = (username, age) => {
console.log(typeof username, typeof age) // ???, ???
}
sayHello('1', 3); // string, number
sayHello(3, null); // number, null
해당 문제는 Node.JS 가 기본적으로 런타임 환경에서 작동하기 때문에 발생합니다.
따라서 실행하는 순간에만 에러를 감지해낼 수 있으며, 이는 큰 단점인 것 같습니다.
하지만 그럼에도 JavaScript 가 매력적인 이유를 찾자면 자유로움 에 있다고 생각합니다.
기본적으로 대다수의 타입은 any 로 존재하며, 이에 따라 변수명만 일치한다면 거의 모든 코드를 작성할 수 있습니다.
이에 높은 생산성 을 보장받게 되는 듯 합니다.
TypeScript 을 조사하면서 가장 많이 본 단어는 다음과 같습니다.
- TypeScript 는 마법의 언어가 아니라, 생산성과 안정성의 트레이드 오프 에 해당하는 선택이다.
- TypeScript 를 단순히 any 를 피하기 위한 용도로만 사용한다면, TypeScript 를 사용해서 얻는 불편함에서 오는 손실이 크다.
- TypeScript 의 다음의 기능은 JavaScript 의 함수 주석 으로 유시하게 구현할 수 있습니다.
(단, eslint 로 이를 강제하지 않으면 에러가 발생하지 않아서 혼란스러울 수 있습니다.)
- 매개변수 타입 및 리턴 타입
const getUserInstance = (name: string): { name: string, age: number } => { return { name: name, age: 423 } }
/** * @params { string } name * @returns { name: string, age: number } */ const getUserInstance = (name) => { return { name: name, age: 423 } }
- 리터털 타입, 유니언 타입
type TGENDER: 'male' | 'female' const gender: TGENDER = '' // 여기에는 male 과 female 만 입력이 가능함
/** @type { 'male' | 'female' } */ const gedner = ''; // 여기에도 male 과 female 만 입력이 가능함
저희는 낮아지는 생산성으로도 마감기한
을 준수할 수 있는지 여부를 먼저 체크했습니다.
API 명세서가 나온 이후, TS 환경에서도 충분히 이를 준수할 수 있다고 판단했고 안정성
을 위해 도입하기로 결정하였습니다.
이 도입으로 인한 기대 이익은 다음과 같았습니다.
- TypeScript 를 도입 하는 데 들어가는 자원 에 비해서 정적 타입 언어로써 가지는 타입 안정성이 높다고 느꼈습니다.
- 협업 할 때, JavaScript 에서는 무슨 자료형인지 알 수 없어서 불편했고 이를 해결할 수 있는 방법이라고 느꼈습니다.
- 항해99 에 주특기 3 주차에 OOP 에 대한 것을 알게 되었고 이 부분을 더 공부하고 싶어서 다양한 추상화 기법이 존재하는 TypeScript 를 배우고 싶었습니다.
🎈🎈🎈🎈🎈🎈🎈 차후 작성 🎈🎈🎈🎈🎈🎈🎈
3.2.1. 데이터 분석 3.2.2. MongoDB vs MySQL 3.2.3. MySQL 선택
cupick 에서 다루는 데이터는 다음과 같습니다.
- 비정형 데이터 : 프로필 이미지, 댓글 이미지
- 정형 데이터 : 유저 정보, 레시피 정보, 댓글 정보
저희 서비스에서 이미지 관련 기능 을 제공하기는 하지만, 이미지 분석 을 통한 기능 이 제공되지는 않습니다.
따라서, 비정형 데이터에 대한 고민 보다는 정형 데이터 를 어떻게 효율적으로 제어할 수 있는 지가 포인트였습니다.
what is the difference between structured data and unstructured data
- https://www.integrate.io/blog/structured-vs-unstructured-data-key-differences/
- https://www.ibm.com/cloud/blog/structured-vs-unstructured-data
- https://www.talend.com/resources/structured-vs-unstructured-data/#:~:text=Structured%20data%20is%20highly%20specific,stored%20in%20their%20native%20formats.
프로젝트 에 대한 전반적인 설계가 끝난 후 저희가 사용해야 할 데이터의 형식을 생각했을 경우 형식이 정해져있는 정형 데이터라고 생각하여
관계형 데이터베이스 의 종류 중 하나인 MySQL 을 선택하게 되었습니다.
cupick 에서 고민한 데이터베이스는 MongoDB 와 MySQL 이었습니다.
- MongoDB : JSON 과 유사한 형태인 BSON 을 기반 의 형태인 Document 들의 집단인 Collection 을 가지고 있습니다.
- MySQL : 표의 형태인 행과 열 로 구성된 테이블 가지고 있습니다.
구분 | 장점 | 단점 |
---|---|---|
MongoDB | 유연한 동적 스키마로 확장성이 좋습니다. TTL 인덱싱 으로 손쉽게 문서의 만료 기한을 지정 |
공식문서 에서 단일 문서 원자성으로 대부분의 Transaction 은 불필요하다고 언급하였고 일부 상황에 한정해서 어려운 Trasaction 을 지원 단일 문서 를 포커스로 두고 있어서, 복잡한 비즈니스 논리(복잡한 단계별 인증 처리) 를 구현하는 데 어렵다. |
MySQL | 구조화된 데이터 처리에 안정적입니다. Transaction 의 사용이 편리하며 안정적입니다. 오랜 역사와 많은 사용으로 인해 참고자료가 많습니다. |
Transaction 으로 인한 락쿼리가 발생합니다. 정규화 작업을 진행하면 정보가 분할되기 떄문에, 조회 시 많은 Row 를 조회하게 됩니다. |
What is the difference MongoDB vs SQL
- https://www.guru99.com/mongodb-vs-mysql.html#:~:text=Key%20Difference%20Between%20MongoDB%20and,data%20in%20tables%20and%20rows.&text=MongoDB%20doesn't%20support%20JOIN,Structured%20Query%20Language%20(SQL).
- https://kinsta.com/blog/mongodb-vs-mysql/
- https://www.interviewbit.com/blog/mongodb-vs-mysql/
- https://www.ibm.com/cloud/blog/mysql-vs-mongodb
- https://www.geeksforgeeks.org/mongodb-vs-mysql/
cupick 에서는 구조화 되어 있는 레시피 정보 가 주 대상이기 떄문에, MySQL 을 사용했습니다.
3.3.1. ORM 에서 Raw Query 까지 3.3.2. Raw Query 에서 느낀 장점 1 - 성능 3.3.3. Raw Query 에서 느낀 장점 2 - 실행 계획 3.3.4. Raw Query vs Query Builder vs ORM
Suquelize 의 N:N 관계를 맺게 되었을 때, model 파일의 수보다 많은 테이블 이 생성되었습니다.
이런 순간을 항해99 미니 및 토이 프로젝트 에서 느겼고 특히, 테이블의 수가 늘어났을 때 혼란스러웠습니다.
이 부분이 직관적이지 않다라고 느껴서 다른 방식이 있는지 찾아보게 되었습니다.
그 끝에 Raw Query 를 작성하게 되면 다음과 같은 장점이 있다는 내용 을 알게 되었습니다.
- Raw Query 가 ORM 보다 성능이 좋게 만들 수 있다.
- Raw Query 로 작성된 코드는 직관적 으로 보인다.
- 복잡한 쿼리문의 경우 ORM 은 너무 길어지게 된다.
그 끝에 Raw Query 선택해서 3주간 주요 기능을 구현하였고 다음과 같은 것을 느꼈습니다.
- ORM 을 안쓰면 생산성이 너무 안좋고 테이블 변화 나 오타 등에 취약해집니다.
- Raw Query 를 쓰면 직관적이기는 하지만, 그 외의 단점이 너무 많습니다.
- 그런데 ORM 에서도 Raw Query 도 쓸 수 있어서 그 부분이 큰 장점인지 모르겠습니다.
구분 | 장점 | 단점 |
---|---|---|
Raw Query | 직관적인 코드 | SQL Injection 에 취약해집니다. Node 안에서 SQL예약어 오타를 잡을 수 없습니다 Node 안에서 SQL 문법이 인식이 되지 않습니다. 실행 전까지, SQL 테이블 및 칼럼의 오타를 잡을 수 없습니다. SQL 테이블 및 칼럼의 변경에 대응하기 어렵습니다. 비즈니스 로직에 따라서 SQL 를 수정하기 어렵습니다. |
Query Builder | SQL Injection 에 대응할 수 있습니다. SQL 예약어 오타를 잡을 수 있습니다. SQL 문법을 인식할 수 있습니다. |
Raw Query 에 비해 코드가 약간 길어집니다. 실행 전까지, SQL 테이블 및 칼럼의 오타를 잡을 수 없습니다. SQL 테이블 및 칼럼의 변경에 대응하기 어렵습니다. 비즈니스 로직에 따라서 SQL 를 수정하기 어렵습니다. |
ORM | 비즈니스 로직에 따라서 migration 을 통해서 반영하기 쉽습니다. DB 와 sync 를 하고 초기 셋팅만 마치면 개발 속도를 높일 수 있습니다. Raw Query 의 대다수 단점이 커버업이 됩니다. |
Raw Query 나 Query Builder 보다 러닝키브가 있습니다. 실제로 어떤 쿼리문이 실행되는 지, 알기가 어렵습니다. 복잡한 쿼리문을 쓰기 어렵고 Query 의 길이가 늘어납니다. |
Cupicsk 서비스는 사용자 프로필과 댓글 작성 시 업로드되는 이미지 를 다루고 있습니다.
저희 서비스는 모바일 디바이스 환경 을 지향하고 있으며, 사용자가 업로드하는 이미지의 사이즈는 디바이스 사이즈 를 기준으로 보입니다.
이 과정에서 사용자가 높은 용량 의 이미지 파일을 올린다면 조금이나마 페이지의 로딩 속도에 영향을 줄 거라고 생각하여 리사이징 의 필요성에 대해 알게 되었습니다.
Express 에서 대중적으로 사용하는 이미지 리사이징 라이브러리는 대표적으로 sharp 와 imagemagick 이 존재하였습니다. npm trends 를 기준으로 2개의 라이브러리를 비교하였을 시, 1년 기준 으로 sharp 는 210만 imagemagick 은 3만 5천 의 다운로드 수를 보여주었습니다. 라이브러리의 최근 업데이트 내역을 살펴보니 sharp 는 약 한 달 전 까지 업데이트 가 있었고 imagemagick 은 10년 전 업데이트가 마지막이 었습니다. 이를 통해 현재까지도 꾸준하게 유지 보수를 하고 있는 sharp 를 선택하였습니다.
10장의 이미지(JPG)의 압축 전/후의 용량을 측정하였습니다.
구분 | 용량 |
---|---|
리사이징 전 | 38mb |
리사이징 후 | 186kb |
기존 38mb 의 용량이었지만 압축 후 186kb 로 감소한 걸 알 수 있습니다.
이미지(JPG) 1장당 평균 용량이 30% 감소(PNG는 평균 15%)했으며, 기존 용량이 클수록 압축률이 높아지는 걸 확인할 수 있었습니다.
이를 통해, 사용자가 높은 용량의 이미지를 업로드하더라도 리사이징하여 이미지의 용량이 감소함에 따라
페이지 랜더링 속도 가 향상되는 걸 확인할 수 있었습니다.
기존에 리사이징 작업을 Node 서버에서 직접 하다 보니 _부하_가 몰려 _병목 현상_이 발생하였습니다.
리사이징 작업의 특성상 서버의 자원(CPU)을 많이 사용하다 보니 _싱글 스레드_인 Node 서버가 감당하기 어렵다고 판단했습니다.
병목 현상을 해결하기 위해 Serverless 기반인 Lambda 함수를 도입하였습니다.
이미지 리사이징 은 4개의 앤드 포인트 요청 시 이루어집니다.
- 회원가입 시
- 프로필 업데이트 시
- 댓글 작성 시
- 댓글 업데이트 시
Node 서버에서 리사이징 한 경우와 Lambda 함수를 통해 진행했을 경우에 대한 성능 측정 내용입니다.
총 10번의 요청을 보내어 요청당 100개의 이미지를 리사이징 한 작업에 대한 _응답 시간_입니다.
기존 Node 서버의 응답 속도는 평균 5.7초 이며, 리사이징 작업을 Lambda 함수로 분리했을 경우 평균 2.1초 가 소요되었습니다.
이를 통해 약 50% 이상의 응답 속도가 향상된 걸 확인할 수 있었습니다.
구분 | 시간(ms) |
---|---|
Node | 5700 |
Lambda | 2100 |
프리 티어 기준 매월 100만 건의 요청에 대해 무료 로 제공하고 있으며, 그 후 초과 요금은 요청 1백만 건당 0.20 USD입니다.
- AWS Lambda image resizing with sharp
- AWS Lambda image resizing with sharp
- AWS Lambda Pricing
프로젝트 초기 사용자의 인가 방식을 구현하는 대표적인 도구 Session 과 JWT 둘 중 어떤 방식으로 구현해야 할지 고민을 하였습니다. Cupicks 프로젝트 가 모바일 앱을 지향하므로 멀티 디바이스 환경에서 문제가 없어야 하고 서버의 개발 지속 방향성인 안정성 을 종합적으로 고려해 JWT 인증 방식을 사용하게 되었습니다.
구분 | 장점 | 단점 |
---|---|---|
Session | 서버가 직접 관리하여 해킹 발생 시 강제로 로그아웃 시키거나 사용자의 계정을 삭제할 수 있습니다. Token 은 Payload 에 사용자에 대한 정보를 넣으므로 아이디만 전달하는 방식인 Session 의 데이터가 용량이 더 낮습니다. |
서버에 저장되므로 서비스의 규모가 커질수록 서버에 무리가 갈 수 있습니다. 사용자의 데이터를 서버 메모리에 저장하므로 용량에 대한 리스크가 있을 수 있습니다. |
JWT | 서버에 직접 저장되지 않으므로 서버 자원과 비용을 절감할 수 있습니다. 사용자에 대한 인증 방식의 확장이 용이합니다. (OAUTH2.0) |
브라우저에 그대로 노출되어 있으므로 위변조 혹은 XSS 공격과 같은 손상의 위험이 높습니다. Token 이 만료되기 전까지 Token 의 유효성을 막을 방법이 없습니다. |
- https://www.geeksforgeeks.org/session-vs-token-based-authentication/
- https://developer.okta.com/blog/2017/08/17/why-jwts-suck-as-session-tokens
대칭 알고리즘이며 RS256 과는 다르게 서명과 검증 모두 Private Key 1가지의 키를 사용합니다.
서버 측과 클라이언트 측 모두 Private Key 를 소유해야 하므로 Private Key 교환을 위해 별도의 안전장치가 필요합니다.
비대칭 알고리즘이며 Private Key 와 Public Key 쌍을 사용합니다.
공급자 에는 서명을 위해 사용되는 Prviate Key 가 있고 소비자 는 Public Key 를 통해 서명이 유효 한지 검사할 수 있습니다.
HS256 과 비교해 생성에는 Private Key 를 사용하고 검증에는 Public Key 2가지의 키를 사용하므로
서버는 Private key 를 클라이언트와 공유할 필요가 없어 보안 적인 측면에서 더 유리합니다.
HS256 와 RS256 2가지의 알고리즘의 차이는 몇 개의 Key 를 사용하는지였습니다.
HS256 은 1개의 Key RS256 은 2개의 Key 어떤 상황에서 어떤 알고리즘 을 사용해야 하는지 공식 문서를 통해 알아본 결과
Private Key 를 사용하는 사람을 제어할 수 있다면 HS256 없다면 RS256 알고리즘을 권장했습니다.
HS256 은 하나의 Key 로 서명/검증 역할을 모두 수행하므로 이 Key 를 클라이언트와 공유하는 과정에서 문제가 발생하면 치명적인 보안 문제가 발생합니다.
반면 RS256 은 서명과 검증에 사용되는 Key 가 다르므로 클라이언트 와 공유 할 필요가 없어 상대적으로 안전 하고
Key 가 손상된 경우 HS256 보다 더 빠르게 교체할 수 있습니다.