Skip to content

IT-COOP/API_server

Repository files navigation

image

🤔ItCoop

사이드 프로젝트와 창업을 위한 사람들의 커뮤니티


❓WHY IT-COOP ?

사이드 프로젝트에 대한 관심과 중요성이 높아지는 요즘!

  • 무에서 유를 창조하는 경험! 내가 직접 기획부터 참여, 배포까지
  • 창업을 함께 할 동료 & 다른 직군의 인맥 생성
  • 커리어 탐색 및 커리어적 성장
  • 채용, 이직을 위한 발돋움
  • 반복되던 삶이 아닌 새로운 자극, 기대감

프로젝트 소개

Architecture

image


프로젝트 기간

  • 3월 3일 ~ 4월 9일 (38일)

기능

간단하고 안전한 회원가입

  • 구글, 깃허브, 카카오를 통한 소셜 로그인으로 간단하고 안전하게 회원가입 할 수 있어요!

😃 나를 나타내는 프로필

  • 나를 어필하는 프로필을 작성 할 수 있어요!

😃 원하는 게시물을 필터 기능으로 간편하게

  • 지역, 직군, keep 많이 한 게시물, 최신 순 별로 프로젝트 게시물을 확인할 수 있어요!

😃 원하는 포지션에 참가 신청하고 함께하는 사람들의 정보 확인

  • 참여하고 싶은 프로젝트에 원하는 포지션으로 참가 신청이 가능해요
  • 같이 프로젝트에 참여하는 사람들의 프로필을 확인할 수 있어요

😳 할지 말지 고민될 때는 바로 KEEP IT!

  • 고민되는 게시물 혹은 참고하고 싶은 게시물을 keep 할 수 있어요 👉 나중에 keep한 게시물은 한번에 모아볼 수 있어요!!

😜 원활한 프로젝트를 위해 팀원들과 소통하기

  • 진행 중인 프로젝트의 팀 별 메신저 기능을 제공해드려요

🤠 ”다음에 또 협업 하고 싶어요!” 함께한 팀원 평가하기

  • 프로젝트 종료 후 팀원들에 대한 평가를 할 수 있어요!

🛠 Tools

디자인


####백엔드



####frontEnd



####Dev tools



이름 포지션 깃허브 or 개인 블로그
황태영 프론트엔드 https://github.com/hty0525
오세명 프론트엔드 https://github.com/nemyung
이승민 백엔드 https://github.com/aaa22220304
이수현 백엔드 https://github.com/lshyun955
천강효 백엔드 https://github.com/beewp
김하연 디자이너
선유경 디자이너 🚢 sbi66@naver.com

🔥이슈 및 트러블슈팅

➡️ DB 확장성 문제

문제 : DB 테이블에서 컬럼 자체가 값을 의미하여 유연성이 제한되는 문제

설명 : 게시글을 DB에 저장할 때, 작성자가 요구하는 각각의 직군 및 기술 스택과 그에 따라 필요한 인원수를 저장해야 헀다. 직군 및 기술 스택을 각각의 컬럼으로 하여 각 몇명이 요구되는 지를 저장하고 있었다. 그 결과, 백엔드에서 컬럼으로 지정한 직군 및 기술 스택에 대한 정보만을 저장하고 있었다.

해결 : 해당하는 컬럼을 모두 제거한 후, 기존 One to One 관계에서 One To Many 관계로 테이블 관계 설정을 바꾸어 기술 스택 및 직군에 대한 정보를 row에 저장하였다.

효과 : 기존의 제약에서 벗어나서 더 다양한 종류의 기술 스택의 정보를 저장할 수 있었고, 실제로 10여 가지의 정보를 저장하였던 초기 대비 프로젝트가 진행됨에 따라 20여 가지의 정보를 저장해야 했고 이를 성공적으로 저장할 수 있었다.

➡️ 페이지네이션 구현 문제

문제 : 여러 가지 필터와 정렬 순서에 따른 페이지네이션을 구현으로 너무 많은 분기처리가 필요했던 문제

설명 : 사용자 편의성을 개선하기 위해서 전체 게시글 목록 조회 API에서 직군, 기술 스택, 모집 완료 여부, 그리고 지역 등의 여러가지 필터 기능이 필요했고, 거기에 더해 최신순 및 keep it 갯수 순으로 정렬이 필요했다. 각각에 대해서 서로 다르게 분기 처리를 할 경우, API를 매우 다양하게 작성하거나, 분기 처리에 따라 코드에 중복되는 부분이 지속적으로 발생하여 코드의 가동성이 떨어지고 에러 처리 등에 있어서 작업 효율이 떨어지는 문제가 발생하였다.

해결 : TypeORM이 제공하는 QueryBuilder를 통해 쿼리를 작성하였다. 각각의 필터링 및 정렬에 대해 조건문을 통해 쿼리에 추가적으로 조건을 지정해주는 방식으로 코드를 작성하였다.

효과 : 각각의 분기처리를 따로 할 필요성이 사라져 코드가 중복되는 부분이 사라졌고, 코드의 가독성이 높아져 코드를 수정함에 있어 불편함이 사라졌다. 또한, 오류가 발생하였을 때 여러가지 조건에 대해서 실험을 진행하여 어떤 부분에서 오류가 발생하였는 지를 손쉽게 찾을 수 있었으며, 똑같은 부분을 여러번 수정할 필요 없이 손쉽게 버그를 고칠 수 있었다.


문제 : 페이지네이션을 Keep it 갯수를 기준으로 할 경우 시간이 매우 오래 걸리는 문제

설명 : 사용자의 편의성을 고려하여 사용자들에게 가장 큰 관심을 받는 게시물을 먼저 볼 수 있게 하는 Keep it 갯수 순으로 전체 목록을 받을 수 있게 하였다. 그러나, DB 구조 상 매번 Keep it의 갯수를 계산해야 하는 문제가 있었고, 그에 따라 쿼리문을 작성할 경우 매 요청에 대해 DB에 저장된 모든 게시글의 Keep it에 대한 정보를 불러오고 계산해야 하는 문제가 있어 시간이 매우 오래 걸렸다.

해결 : 게시글에 대한 정보를 저장할 때, Keep it에 대한 갯수를 저장하는 컬럼을 추가하여 이를 기준으로 정렬을 쉽게 할 수 있도록 하였다.

효과 : 각각의 요청에 있어 매번 DB의 모든 게시글에 대해서 Keep it의 갯수를 계산해야 했던 기존 방식 대비 훨씬 빠른 속도로 요청이 처리되는 것을 확인하였고, 서버의 부하 또한 감소시킬 수 있었다.

➡️ Bulk Insert와 트렌젝션 문제

문제 : 여러 테이블에 한번에 값을 저장 할 때 발생한 문제

설명 : 게시글에 대한 정보를 저장할 때, 게시글 테이블과 One to Many 관계에 있는 테이블에 기타 정보를 저장하는 과정에서 시간이 너무 오래 걸리고, 문제가 발생할 경우 게시글은 업로드 되지만, 다른 테이블에 들어가는 정보는 제대로 저장되지 않는 문제가 발생하였다. 또한, 최초 for문을 이용해 insert 쿼리를 실시하여 각각에 대해서 통신 시간이 소요되었기 때문에 너무 오랜 시간이 소요되는 문제가 발생하였다.

해결 : TypeORM이 지원하는 Bulk Insert를 사용함으로써 통신 시간을 단축시킬 수 있었다. 이에 더해 Promise.all이나 Promise.allSettled를 이용해 해당 트렌젝션 내에서의 시간을 더욱 단축하려 시도하였다. 그러나, Promise.all을 사용하는 경우에는 롤백 과정에서 통신 시간이 불규칙하게 되어 일부 롤백되어야 하는 쿼리가 누락되어 롤백되지 않는 문제가 발생하였다. 또한, 하나의 트랜젝션 내에서 Promise.all을 이용하더라도 하나의 통신을 통해 모든 쿼리가 실행되기 때문에, 시간 단축에 어려움이 있었다. 이를 위해서 여러개의 트랜젝션을 이용하는 경우에는 실질적으로 트랜젝션을 이용하는 이유은 롤백을 실시할 수 없다는 문제가 있었다. 이를 고려하여 Promise.all을 이용한 통신 시간 단축은 달성할 수 없었다.

효과 : API가 요청되었을 때, DB와의 통신으로 인한 딜레이가 많이 감소하여 기존 방식 대비 훨씬 빠른 속도로 요청이 처리되는 것을 확인할 수 있었고, 서버의 부하 또한 감소시킬 수 있었다.

➡️ 수정 및 삭제 시 롤백 문제

문제 : 하나의 요청에 대해서 DB에 여러번 업데이트가 필요한 경우, 중간에 에러가 발생했을 때 의도치 않은 정보 수정이 발생했던 문제

설명 : 게시글에 Keep it에 대한 컬럼을 추가하여 Keep it을 하거나 취소했을 경우, 해당 컬럼의 정보 또한 수정해야 했다. 또한, 게시글을 수정할 때 여러가지 정보를 한번에 수정할 경우 하나의 테이블이 아니라 One to Many 관계에 있는 서로 다른 테이블의 정보를 수정해야 하는 경우가 존재했다. 그러나, 중간에 에러가 발생하여 작업이 중지된 경우, 어떤 부분에서 에러가 발생한 것인지 알 수 없으며 그 이전에 실시된 수정 사항에 대해서는 반영되는 문제가 발생하였다.

해결 : Transaction을 통해 문제를 해결하였다. Transaction은 My SQL에서 지원하는 기능으로, TypeORM에서도 이용할 수 있었다. Transaction은 그 특성 상 해당하는 모든 쿼리문이 반영되거나, 에러가 발생할 경우 모두 반영되지 않는데 이러한 특성이 문제 해결에 주요하게 작용하였다. try catch문을 통해 에러를 감지하고 에러가 발생할 경우, transaction을 롤백시켜 모든 수정사항을 되돌릴 수 있었다.

효과 : 게시글에 대한 정보 테이블에서 Keep it의 갯수에 대해 저장된 정보가 정확하게 Keep it 갯수를 반영하게 되었다. 사용자가 작성한 게시글 수정사항이 반영되는 도중에 에러가 발생하더라도 부분적으로 반영되는 것이 아니라 다시 원래 상태로 정상적으로 돌아오게 되었다.

➡️ 대댓글 구현 문제

문제 : 대댓글을 구현함에 있어 일차원 배열의 정보를 트리 구조로 변환해야 하여 시간 복잡도가 O(n²)이던 문제

설명 : DB에 댓글에 대한 정보를 저장할 때, depth를 저장하여 해당 댓글이 댓글인지 대댓글인지를 저장하고, group을 저장하여 해당 댓글 및 대댓글이 몇번째 댓글 및 대댓글인지에 대한 정보를 저장하였다. 이 경우, 프론트엔드에서 일차원 배열을 이중 for문을 활용해 트리 구조로 변환해야 했기 때문에 시간 복잡도가 O(n²)이었고, 댓글의 수가 많아질 경우 문제가 발생할 것으로 판단하였다. 또한, 댓글이 삭제된 대댓글을 표현할 수 있는 방법이 필요했다.

해결 : 문제를 해결하기 위해 DB 구조 수정과 트리 구조로 변환 로직 변경을 고려하였다. Type ORM은 또한 tree 구조를 지원하여 이렇게 DB 구조를 변환할 경우 문제를 해결할 수 있다. 그러나, 이렇게 될 경우 매핑에 상당히 오랜 시간이 소요될 것으로 판단하였다. 멘토님께 질문한 결과, Type ORM은 매핑에 있어 row의 수가 많아질 수록 기하급수적으로 시간이 소요됨을 알게 되어, 이 방법보다는 로직을 변환하는 방식으로 문제를 해결하기로 하였다. Javascript는 Set 자료구조를 지원하는데, set 자료형의 경우 각각의 요소가 있는 지 여부를 검사하는 것과 add가 O(1)의 시간을 소요한다. 또한 댓글은 시간 순서대로 저장되어 있는데, 이 점을 이용하여 기존 이중 for문에서 단일 for문으로 대댓글 구현하였다.

효과 : 단일 for문으로 대댓글 로직을 변경하여 기존 O(n²)이던 시간 복잡도를 O(n)으로 개선할 수 있었다. ORM에서 추가적인 매핑 과정 없이 문제를 해결할 수 있어 다른 부분에서 시간이 추가적으로 소요되는 부분을 걱정할 필요가 없었다.

➡️ 부하 테스트

문제 : 부하 테스트 라이브러리인 artillery를 통해 서버에 부하를 가했을 때, 요청 처리 시간이 너무 오래 걸리던 문제

설명 : 부하 테스트 라이브러리인 artillery를 통해 요청이 가장 잦을 것으로 생각되는 전체 게시글 목록 조회 API에 대한 부하 테스트를 진행하였다. 기존 200ms 이하이던 요청 처리 시간이 평균 1500ms으로 증가하였고, 하위 99%의 경우 3000ms 이상이 소요되었다. 또한, 부하를 더 강하게 했을 때는 요청이 정상적으로 응답을 받을 수 없는 문제가 발생하였다.

해결 : TypeORM의 로깅 기능으로 해당 API에서 이용되는 쿼리문을 읽을 수 있었다. 해당 쿼리문에서 이용되는 조건문 및 정렬 기준을 토대로 테이블의 index를 점검 및 수정하였다.

효과 : 부하 테스트를 재실시하였을 때, 평균 소요 시간을 20% 정도 감축할 수 있었으며, 하위 99%의 경우에는 최대 50%까지 시간 소요가 감소하는 것을 확인하였다.

more info

DB ERD

image

About

Source code of API server for it-coop

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors