Skip to content

3. 라이브러리 선택

axisotherwise edited this page Oct 19, 2022 · 30 revisions

목차 원본 : https://github.com/cupicks/.github/tree/main/profile/libs

프로젝트 팀 결성 이후, @unchaptered@axisotherwise 는 프로젝트 초기 설정, Lib 선택, 으로 많은 이야기를 나누었습니다.

서로 원하는 기술 스텍 이나 흥미를 느껴하는 부분이 너무나 달랐기 때문에 협의 과정이 어려웠습니다.

따라서, 서로가 모두 동감하는 아이덴티티 (지향점) 을 고르고 이를 원칙으로 삼자고 정했습니다.

저희 둘의 목표는 안정적인 개발 이었으며, 나아가서 cupick 의 지향점도 동일했습니다.

이에 따라 주제 선정 이후 다음과 같은 의사결정을 하게 되었습니다.

  1. 타입스크립트
  2. MySQL 선택
  3. Raw Query 선택
  4. AWS Lambda 이미지 리사이징
  5. JWT RS256 선택

3.1. 타입스크립트

우리의 아이덴티티는 안정적인 개발 입니다.

따라서 일회성 서버가 아니라, 안전하고 지속적으로 관리할 수 있는 서버를 만들고 싶었습니다.

  • 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

3.1.1. JavaScript 의 고질적인 불편함

안전한 서버란, 배포 전에 최대한 많은 에러를 잡아낼 수 있어야 한다고 생각합니다.

하지만 JS 를 통해서 개발을 해보면 다음과 같은 불편함을 느끼게 됩니다.

const sayHello = (username, age) => {
  console.log(typeof username, typeof age) // ???, ???
}

sayHello('1', 3); // string, number
sayHello(3, null); // number, null

해당 문제는 Node.JS 가 기본적으로 런타임 환경에서 작동하기 때문에 발생합니다.

따라서 실행하는 순간에만 에러를 감지해낼 수 있으며, 이는 큰 단점인 것 같습니다.

3.1.2. JavaScript 의 독보적인 장점

하지만 그럼에도 JavaScript 가 매력적인 이유를 찾자면 자유로움 에 있다고 생각합니다.

기본적으로 대다수의 타입은 any 로 존재하며, 이에 따라 변수명만 일치한다면 거의 모든 코드를 작성할 수 있습니다.

이에 높은 생산성 을 보장받게 되는 듯 합니다.

3.1.3. TypeScript 을 선택하게 된다면

TypeScript 을 조사하면서 가장 많이 본 단어는 다음과 같습니다.

  • TypeScript 는 마법의 언어가 아니라, 생산성과 안정성의 트레이드 오프 에 해당하는 선택이다.
  • TypeScript 를 단순히 any 를 피하기 위한 용도로만 사용한다면, TypeScript 를 사용해서 얻는 불편함에서 오는 손실이 크다.
  • TypeScript 의 다음의 기능은 JavaScript 의 함수 주석 으로 유시하게 구현할 수 있습니다. (단, eslint 로 이를 강제하지 않으면 에러가 발생하지 않아서 혼란스러울 수 있습니다.)
    1. 매개변수 타입 및 리턴 타입
    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
       }
    }
    1. 리터털 타입, 유니언 타입
    type TGENDER: 'male' | 'female'
    const gender: TGENDER = '' // 여기에는 male 과 female 만 입력이 가능함
    /** @type { 'male' | 'female' } */
    const gedner = ''; // 여기에도 male 과 female 만 입력이 가능함

저희는 낮아지는 생산성으로도 마감기한 을 준수할 수 있는지 여부를 먼저 체크했습니다.
API 명세서가 나온 이후, TS 환경에서도 충분히 이를 준수할 수 있다고 판단했고 안정성 을 위해 도입하기로 결정하였습니다.

이 도입으로 인한 기대 이익은 다음과 같았습니다.

  1. TypeScript 를 도입 하는 데 들어가는 자원 에 비해서 정적 타입 언어로써 가지는 타입 안정성이 높다고 느꼈습니다.
  2. 협업 할 때, JavaScript 에서는 무슨 자료형인지 알 수 없어서 불편했고 이를 해결할 수 있는 방법이라고 느꼈습니다.
  3. 항해99 에 주특기 3 주차에 OOP 에 대한 것을 알게 되었고 이 부분을 더 공부하고 싶어서 다양한 추상화 기법이 존재하는 TypeScript 를 배우고 싶었습니다.

3.1.4. TypeScript 를 도입하고 나서

🎈🎈🎈🎈🎈🎈🎈 차후 작성 🎈🎈🎈🎈🎈🎈🎈


3.2. MySQL 선택

3.2.1. 데이터 분석 3.2.2. MongoDB vs MySQL 3.2.3. MySQL 선택

3.2.1. 데이터 분석

cupick 에서 다루는 데이터는 다음과 같습니다.

  • 비정형 데이터 : 프로필 이미지, 댓글 이미지
  • 정형 데이터 : 유저 정보, 레시피 정보, 댓글 정보

저희 서비스에서 이미지 관련 기능 을 제공하기는 하지만, 이미지 분석 을 통한 기능 이 제공되지는 않습니다.
따라서, 비정형 데이터에 대한 고민 보다는 정형 데이터 를 어떻게 효율적으로 제어할 수 있는 지가 포인트였습니다.

what is the difference between structured data and unstructured data

3.2.2. MongoDB vs MySQL

프로젝트 에 대한 전반적인 설계가 끝난 후 저희가 사용해야 할 데이터의 형식을 생각했을 경우 형식이 정해져있는 정형 데이터라고 생각하여

관계형 데이터베이스 의 종류 중 하나인 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

3.2.3. MySQL 선택

cupick 에서는 구조화 되어 있는 레시피 정보 가 주 대상이기 떄문에, MySQL 을 사용했습니다.


3.3. Raw Query 선택

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

3.3.1. ORM 에서 Raw Query 까지

Suquelize 의 N:N 관계를 맺게 되었을 때, model 파일의 수보다 많은 테이블 이 생성되었습니다.
이런 순간을 항해99 미니 및 토이 프로젝트 에서 느겼고 특히, 테이블의 수가 늘어났을 때 혼란스러웠습니다.

이 부분이 직관적이지 않다라고 느껴서 다른 방식이 있는지 찾아보게 되었습니다.
그 끝에 Raw Query 를 작성하게 되면 다음과 같은 장점이 있다는 내용 을 알게 되었습니다.

  • Raw Query 가 ORM 보다 성능이 좋게 만들 수 있다.
  • Raw Query 로 작성된 코드는 직관적 으로 보인다.
  • 복잡한 쿼리문의 경우 ORM 은 너무 길어지게 된다.

그 끝에 Raw Query 선택해서 3주간 주요 기능을 구현하였고 다음과 같은 것을 느꼈습니다.

  • ORM 을 안쓰면 생산성이 너무 안좋고 테이블 변화오타 등에 취약해집니다.
  • Raw Query 를 쓰면 직관적이기는 하지만, 그 외의 단점이 너무 많습니다.
  • 그런데 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

구분 장점 단점
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 의 길이가 늘어납니다.

3.4. AWS Lambda / sharp

3.4.1. 이미지 리사이징 도입 계기

Cupicsk 서비스는 사용자 프로필과 댓글 작성 시 업로드되는 이미지 를 다루고 있습니다.
저희 서비스는 모바일 디바이스 환경 을 지향하고 있으며, 사용자가 업로드하는 이미지의 사이즈는 디바이스 사이즈 를 기준으로 보입니다. 이 과정에서 사용자가 높은 용량 의 이미지 파일을 올린다면 조금이나마 페이지의 로딩 속도에 영향을 줄 거라고 생각하여 리사이징 의 필요성에 대해 알게 되었습니다.

3.4.2. 라이브러리 선택

Express 에서 대중적으로 사용하는 이미지 리사이징 라이브러리는 대표적으로 sharpimagemagick 이 존재하였습니다. npm trends 를 기준으로 2개의 라이브러리를 비교하였을 시, 1년 기준 으로 sharp 는 210만 imagemagick 은 3만 5천 의 다운로드 수를 보여주었습니다. 라이브러리의 최근 업데이트 내역을 살펴보니 sharp 는 약 한 달 전 까지 업데이트 가 있었고 imagemagick 은 10년 전 업데이트가 마지막이 었습니다. 이를 통해 현재까지도 꾸준하게 유지 보수를 하고 있는 sharp 를 선택하였습니다.

3.4.3. 용량 측정

10장의 이미지(JPG)의 압축 전/후의 용량을 측정하였습니다.

구분 용량
리사이징 전 38mb
리사이징 후 186kb
리사이징 전 이미지 리사이징 후 이미지

기존 38mb 의 용량이었지만 압축 후 186kb 로 감소한 걸 알 수 있습니다.

3.4.4. 결과

이미지(JPG) 1장당 평균 용량이 30% 감소(PNG는 평균 15%)했으며, 기존 용량이 클수록 압축률이 높아지는 걸 확인할 수 있었습니다. 이를 통해, 사용자가 높은 용량의 이미지를 업로드하더라도 리사이징하여 이미지의 용량이 감소함에 따라
페이지 랜더링 속도 가 향상되는 걸 확인할 수 있었습니다.

3.5.5. AWS Lambda 도입 계기

기존에 리사이징 작업을 Node 서버에서 직접 하다 보니 _부하_가 몰려 _병목 현상_이 발생하였습니다.
리사이징 작업의 특성상 서버의 자원(CPU)을 많이 사용하다 보니 _싱글 스레드_인 Node 서버가 감당하기 어렵다고 판단했습니다.
병목 현상을 해결하기 위해 Serverless 기반인 Lambda 함수를 도입하였습니다.

이미지 리사이징 은 4개의 앤드 포인트 요청 시 이루어집니다.

  1. 회원가입 시
  2. 프로필 업데이트 시
  3. 댓글 작성 시
  4. 댓글 업데이트 시

3.4.6. 성능 측정

Node 서버에서 리사이징 한 경우와 Lambda 함수를 통해 진행했을 경우에 대한 성능 측정 내용입니다.


총 10번의 요청을 보내어 요청당 100개의 이미지를 리사이징 한 작업에 대한 _응답 시간_입니다.

10번 리사이징 요청
10번 일반 요청

3.4.7. 결과

기존 Node 서버의 응답 속도는 평균 5.7초 이며, 리사이징 작업을 Lambda 함수로 분리했을 경우 평균 2.1초 가 소요되었습니다.
이를 통해 약 50% 이상의 응답 속도가 향상된 걸 확인할 수 있었습니다.

구분 시간(ms)
Node 5700
Lambda 2100

3.5.8. Lambda 요금

프리 티어 기준 매월 100만 건의 요청에 대해 무료 로 제공하고 있으며, 그 후 초과 요금은 요청 1백만 건당 0.20 USD입니다.


- AWS Lambda image resizing with sharp
- AWS Lambda image resizing with sharp
- AWS Lambda Pricing


3.5. JWT RS256 선택

프로젝트 초기 사용자의 인가 방식을 구현하는 대표적인 도구 SessionJWT 둘 중 어떤 방식으로 구현해야 할지 고민을 하였습니다. Cupicks 프로젝트 가 모바일 앱을 지향하므로 멀티 디바이스 환경에서 문제가 없어야 하고 서버의 개발 지속 방향성인 안정성 을 종합적으로 고려해 JWT 인증 방식을 사용하게 되었습니다.

3.5.1. Session vs JWT

구분 장점 단점
Session 서버가 직접 관리하여 해킹 발생 시 강제로 로그아웃 시키거나 사용자의 계정을 삭제할 수 있습니다.
Token 은 Payload 에 사용자에 대한 정보를 넣으므로 아이디만 전달하는 방식인 Session 의 데이터가 용량이 더 낮습니다.
서버에 저장되므로 서비스의 규모가 커질수록 서버에 무리가 갈 수 있습니다.
사용자의 데이터를 서버 메모리에 저장하므로 용량에 대한 리스크가 있을 수 있습니다.
JWT 서버에 직접 저장되지 않으므로 서버 자원과 비용을 절감할 수 있습니다.
사용자에 대한 인증 방식의 확장이 용이합니다. (OAUTH2.0)
브라우저에 그대로 노출되어 있으므로 위변조 혹은 XSS 공격과 같은 손상의 위험이 높습니다.
Token 이 만료되기 전까지 Token 의 유효성을 막을 방법이 없습니다.

3.5.2. Jwt 암호화 알고리즘

  • HS256

대칭 알고리즘이며 RS256 과는 다르게 서명과 검증 모두 Private Key 1가지의 키를 사용합니다.
서버 측과 클라이언트 측 모두 Private Key 를 소유해야 하므로 Private Key 교환을 위해 별도의 안전장치가 필요합니다.

  • RS256

비대칭 알고리즘이며 Private KeyPublic Key 쌍을 사용합니다.
공급자 에는 서명을 위해 사용되는 Prviate Key 가 있고 소비자Public Key 를 통해 서명이 유효 한지 검사할 수 있습니다.
HS256 과 비교해 생성에는 Private Key 를 사용하고 검증에는 Public Key 2가지의 키를 사용하므로
서버는 Private key 를 클라이언트와 공유할 필요가 없어 보안 적인 측면에서 더 유리합니다.

선택

HS256RS256 2가지의 알고리즘의 차이는 몇 개의 Key 를 사용하는지였습니다.

HS256 은 1개의 Key RS256 은 2개의 Key 어떤 상황에서 어떤 알고리즘 을 사용해야 하는지 공식 문서를 통해 알아본 결과

Private Key 를 사용하는 사람을 제어할 수 있다면 HS256 없다면 RS256 알고리즘을 권장했습니다.

HS256 은 하나의 Key 로 서명/검증 역할을 모두 수행하므로 이 Key 를 클라이언트와 공유하는 과정에서 문제가 발생하면 치명적인 보안 문제가 발생합니다.

반면 RS256 은 서명과 검증에 사용되는 Key 가 다르므로 클라이언트공유 할 필요가 없어 상대적으로 안전 하고

Key 가 손상된 경우 HS256 보다 더 빠르게 교체할 수 있습니다.