백엔드 개발 포트폴리오 | 운동시설 중개 플랫폼

내일배움캠프 4기 웹 개발 과정 Node.js 트랙 수료생 최종 프로젝트 '식스팩'을 소개합니다.
Aug 16, 2023
백엔드 개발 포트폴리오 | 운동시설 중개 플랫폼

💡 프로젝트 소개

식스팩‘ 은 전국의 운동시설을 중개하는 구독형 플랫폼 서비스 입니다.


서비스 기획 배경
📢
헬스장, 필라테스, 크로스핏 모두 하고싶은 사람 모여라!
어제는 회사 앞 필라테스, 오늘은 집 앞 헬스장, 내일은 새로생긴 크로스핏을 해볼까!?
아직도 매일 똑같은 곳에서 똑같은 운동을 한다고? 🤦‍♀️ 출장이 잦아서 운동을 하고싶어도 못 한다고?🤷‍♀️
지금 당장 식스팩에서 하고싶은 운동 마음껏 하자! 💪💪💪
 
서비스 둘러보기
사용자 가이드
notion image
정기결제 (구독 시스템)
💳
결제 시스템
  • 정기 구독형 서비스(매달 1일 자동결제)
  • 가맹점에 원활한 정산을 하기 위해 매 달 기준으로 정산
  • 때문에 월 중간에 구독시 첫 결제는 남은 일 수 만큼만 계산
  • 구독 취소시 해당 월까지는 이용 가능하며 다음 달 부터 멤버십 해지
  • 테스트 결제가 가능한 PG사 선정 (토스페이먼츠 or 아임포트)
  • 아임포트 선정 이유 : 두 곳의 개발자용 문서를 모두 검토한 결과 기능별 설명과 관리자 페이지의 기능이 더 잘 갖추어진 아임포트가 주니어 입장에서 조금 더 깊은 이해를 할 수 있을것 같아 아임포트로 선정하였음
QR코드
QR코드를 이용한 입장처리
QR코드를 이용한 입장처리
개요
  • 멤버쉽 별 이용가능 운동 시설 분리를 위해 출입 회원의 데이터를 받아야 함
  • 회원의 편의성을 위해 간편하게 찍고 들어갈 수 있는 QR코드를 선택
  • 가맹점에서는 로그인 된 디바이스에 카메라만 있으면 회원출입관리 가능
구현
  • 멤버십 이용 회원 로그인 시 메인화면에 QR코드 생성
    • notion image
 
  • 비즈니스유저의 가맹점 리스트에 각 매장의 QR코드 스캐너 생성
    • notion image
 
  • 회원 검증 로직을 거쳐 입장에 성공하면 DB에 가맹점 정보와 회원정보 저장
    • notion image
  • 원활한 테스트를 위해 임시 QR코드 검증버튼 생성
    • notion image
사용된 기술
  • 생성된 QR코드에는 회원의 id가 들어가 있음
  • QR코드 스캔을 위해 오픈소스 라이브러리인 jsQR 도입
  • QR코드를 이용한 출입관리 로직
    • notion image
검색 및 현재 위치기반 업체 리스트 가져오기
🔎
검색기능
  • 검색은 Elasticsearch를 이용하여 구현
  • 업체이름이나 주소의 일부분만 일치해도 검색결과를 반환(와일드카드)
  • 자동완성된 업체이름을 클릭 시 해당업체만 검색결과에 반환(구문 검색)
notion image
 
코드
notion image
 
현재 위치기반 업체 리스트 가져오기
현재 위치기반 업체 리스트 가져오기
  • geolocation API를 이용하여 현재위치를 가져옴
  • 현재위치 텍스트를 백엔드에 전달하여 엘라스틱서치의 매치쿼리를 이용해서 업체 리스트를 반환 (예시 : “전주시” 를 검색하여 리스트 반환)
    • notion image
코드
notion image
식스팩 일반회원 둘러보기
식스팩 관리자 페이지 둘러보기
식스팩 사업자 페이지 둘러보기

📌 서비스 아키텍처

notion image
기술스택
notion image

🔧 기술적 의사결정

모바일 기반 서비스
이유
  • 실제 서비스 사용성을 고려했을때, 모든 서비스가 모바일에서 이루어진다고 판단
🚨
고민
  • 추후 앱 출시를 고려하였을때 앱은 서버에서 쿠키를 다룰 수 없음
✏️
해결방안
  • 서버는 쿠키에 담아야하는 내용을 응답에 담아 프론트에 전달하고
  • 프론트에서 토큰을 받고 로컬스토리지에 저장해서 필요할 때마다 요청 헤더에 담아서 보내줌
의사결정
  • 쿠키대신 서버와 통신하지 않는 로컬스토리지에 토큰 저장
  • 보안상의 이슈로 추후 로컬스토리지 ⇒ 쿠키로 옮길 예정
Access Token & Refresh Token
도입이유
  • 보안을 유지하면서 사용자의 편의성 향상을 위해 도입
🚨
고민
  • 엑세스토큰만 사용하는경우, 유효기간이 만료될 때까지 권한을 도용당할 수 있어 보안이 취약해질 수 있고, 유효기간이 짧으면 사용자가 로그아웃이 되는 상황이 발생
✏️
해결방안
  • 리프레쉬토큰을 사용하여 엑세스토큰이 만료되면 리프레쉬토큰과 함께 서버에 요청하여 새로운 엑세스토큰을 발급
🗣
의견조율
  • 리프레쉬토큰을 어디서 관리해야할까? (프론트 vs 백엔드)
    • 프론트 : 쿠키, 로컬스토리지
    • 백엔드 : mysql, Redis
의사결정
  1. 프론트에 토큰 저장
      • stateless한 jwt토큰의 특성 때문에 서버의 부하를 줄일 수 있음
      • 토큰이 탈취당했을 경우 아무런 조치를 취할 수 없음
  1. 백엔드에 토큰 저장
      • 토큰이 탈취당했을 경우 리프레쉬토큰을 삭제시킬 수 있음
      • Redis vs mysql
        • mysql에 비해 Redis에 저장 시 속도가 더 빠름
        • Redis에 저장 시 DB와의 엑세스 횟수를 줄일 수 있어서 부하를 줄일 수 있음
       
따라서 Refresh Token은 Redis에 암호화하여 저장하기로 결정
notion image
 
cache-aside pattern
도입이유
  1. DB에 직접 접근하는 것보다 캐시에서 데이터를 읽는 것이 더 빠르고 효율적
  1. 캐시에 데이터가 없는 경우에만 DB에서 데이터를 가져오기때문에 DB부하를 줄일 수 있음
  1. 캐시와 DB의 동기화 문제를 해결할 수 있음
🚨
고민
  • 캐시에 보관된 이전 데이터로 인해 데이터 수정,삭제,추가 시 데이터가 즉각적으로 동기화되지 않음
✏️
해결방안
  • write-through 전략을 이용해서 데이터가 업데이트되면 DB에 수정사항을 저장하고 해당 캐시를 삭제
<예시>
  1. 조회한 데이터는 캐시에 저장
notion image
  1. 데이터 수정, 삭제, 추가 시에 동기화
notion image
🗣
의견조율
  • Redis vs Memcached
의사결정
  • 다양한 데이터 유형을 지원하는가?
    • Redis는 문자열, 목록 등 많은 데이터 유형을 지원
    • Memcached는 키-값 쌍만 지원
  • 영속성을 지원하는가?
    • 우리는 Refresh Token을 캐시에 저장하기때문에 영속성을 지원해야함
      • Redis 지원O , Memcached 지원X
 
따라서 Redis를 이용하기로 결정
Elasticsearch
도입이유
  • 기존 방식인 mysql에서 LIKE를 이용한 검색기능의 성능 향상을 위해 도입
    • Elasticsearch는 풀텍스트 검색에 뛰어나고, 거의 실시간으로 색인하고 검색할 수 있음
🚨
고민
  1. mysql과 Elasticsearch의 데이터 동기화 방법 결정
      • Logstash
      • 애플리케이션 레이어에서 실시간으로 동기화
      • 엔티티 리스너 사용
✏️
해결방안, 🗣 의견조율, ✅ 의사결정
  1. mysql과 Elasticsearch의 데이터 동기화 방법 결정
    1. → 세가지 방법을 비교해본 결과
      • Logstash는 관리 비용이 높고
      • 엔티티 리스너는 성능문제가 있을 수 있겠다라고 판단
      • 따라서 애플리케이션 레이어에서 실시간으로 동기화하는 방법을 채택
        • 데이터의 일관성을 보장할 수 있고, 구현이 어렵지 않아서 촉박했던 시간안에 구현할 수 있을 것이라고 판단
        • notion image
AWS EC2 ⇒ GCP VM인스턴스로 변경
도입이유
  • EC2 프리티어로 docker-compose를 통한 배포(nestjs, redis, elasticsearch)를 하기엔 인스턴스의 리소스가 부족
🚨
고민
  • nestjs, redis, elasticsearch모두 메모리를 많이 사용하는 애플리케이션이므로 EC2 t2.micro, t3.micro 인스턴스에서 실행하면 성능저하, 서버꺼짐의 현상이 일어났고, 추가비용 발생 등의 문제가 우려
✏️
해결방안
  • 성능이 조금 더 좋은 서버를 사용(무료로 사용가능한 GCP의 서버 사양이 AWS의 프리티어 사양보다 4배정도 성능이 좋음) → GCP(e2-medium) vs AWS(t2-micro)
🗣
의견조율
  1. GCP에 가입하면 제공해주는 크레딧으로 e2-medium vm머신(vCPU 2개, 4GB 메모리)을 무료로 사용
  1. AWS에 돈을 조금 지불하더라도 EC2의 다른 서버를 사용
의사결정
  • 국내 점유율 2위인 GCP를 사용해 보다 다양한 경험을 해보기로 결정
⚠️
추가 고려사항
  • DB는 AWS RDS를 사용하고 있는 상황이다보니 현재 public으로 설정되어 있지만 최종배포 후 에는 GCP 에서 DB로 전송되는 고정ip만 rds에 접근이 가능하도록 아웃바운드 보안규칙에 설정 필요하다 생각됨
 

🛠️ 트러블 슈팅

정기결제
💳
결제를 진행하면 DB에 데이터가 두번 저장되는 이슈 발생
  • 예상 결제 로직 : IMP.request_pay() ⇒ /complete ⇒ /webhook
  • 실제 결제 로직 : IMP.request_pay() ⇒ /webhook ⇒ /complete
  • 아임포트 문의 결과 웹훅(/webhook)을 사용하여 정기결제에 관련된 모든 API를 처리해야 한다는 답변을 받음
  • 아임포트 개발 가이드 및 스웨거를 보고 머릿속에 그린 결제 로직과 실제 로직 비교
    • notion image
 
Admin 카테고리 별 업체 순위 조회 성능 개선
🛠
가맹점 개수가 많아지니 가맹점 순위 데이터 가져오는데 오래걸림
변경 전 코드
  • 전체 가맹점 정보를 서버에서 넘겨주고 프론트에서 sorting
  • async-await 비동기 방식을 Promise.all 방식으로 수정하여 테스트 해보았지만 유의미한 결과가 나오지 않음 (기존로직이 잘못되었다 판단)
notion image
변경 후 코드
  • 인자로 받은 카테고리의 상위 10개 가맹점의 데이터만 전송
notion image
JMeter 테스트 결과
💡 상황 : 2023년 2월 가맹점 별 정산금액 조회 (전체 ⇒ top10) 평균 속도 155,937ms468ms 333배 성능 향상
notion image
notion image
notion image
검색기능 향상
🛠
검색기능의 성능향상을 위해 Elasticsearch 이용
before (mysql)
notion image
after (Elasticsearch)
notion image
JMeter 테스트
👉
상황 : “주소”, “업체이름”에 “서울”이 포함된 모든 업체 검색
(0.01초마다 10초동안 1명의 유저가 test 5번 반복)
결과 : 평균 약 23배 속도차이
(데이터 수가 많아질수록 차이가 심해질 것으로 예상)
notion image
 
🛠
번외) match vs wildcard (2배 속도차이) 하지만 ms 단위여서 큰 차이는 없다고 판단하여 편의성이 더 뛰어난 wildcard쿼리로 결정
(추후에 ngram방식으로 변경 예정)
notion image
인피니트 스크롤
💡
필요한 만큼의 데이터만 전달해주는 방식으로 변경
before
  • 전체 데이터를 넘겨주고 프론트에서 무한스크롤 적용
notion image
after
  • 스크롤이 특정 위치까지 가면 추가로 데이터 요청해서 가져오는 방식
  • 보여지는 개수보다 1개 더 요청해 다음페이지가 있는지 확인하는 방식
notion image
JMeter 테스트
1초에 2000명의 유저가 값을 불러온 결과 평균 약 3.4배의 속도 차이
전체 데이터량은 약 900개 정도였으며, 데이터가 늘어나면 늘어날수록 속도 차이가 점점 더 심해질 것으로 예상.
notion image
이미지 리사이징
🛠
이미지파일의 용량을 줄여서 웹페이지 로딩속도 개선
Sharp라이브러리 사용
before
notion image
after
notion image
Share article
Subscribe to our newsletter
RSSPowered by inblog