Skip to content

[J123] 이태훈

LeeTH916 edited this page Dec 20, 2023 · 8 revisions

1주차 회고

성장 목표

  • BE 개발의 전반적인 프로세스와 협업 방식에 대해 경험해보기
  • 적절한 DB를 선정하고 DB 쿼리 최적화해보기
  • 테스팅에 대해 많은 경험 해보기

이번 주 기술적 고민거리

  • api 설계
    • login 부분에 대한 api 설계시, 일반 로그인과 소셜로그인의 엔드포인트를 구분해야 할지에 대해 고민 -> 관리와 코드 재사용성 측면에서 하나로 하는게 좋을 것 같다고 판단하여 하나로 통합
    • 유저 팔로우, 언팔로우 같은 작업에 대해 엔드포인트를 어떻게 설정할 지 고민 -> 엔드포인트의 이름을 서로 다르게 하는 방법을 생각하다가, RESTful한 api 설계를 위해 엔드포인트는 같게 하고 메서드를 분리하는 방향으로 결정
  • DB 선택
    • 주제가 맛집 공유 플랫폼이기 때문에 지도에 대한 위치 데이터를 저장하는 측면에서 NoSQL을 사용해야 하는지에 대해 고민 -> 내부적으로는 한번 사용해보고 싶다라는 의견도 있었지만, 우선 가장 중요한 RDB를 많이 공부하고 사용하는 형태로 구현해보고 추후에 발전시켜나가는 방향으로 변경
  • 웹 서버 사용 여부
    • 단순히 사용해보고 싶다라는 생각으로 어떤 웹 서버를 사용할 지에 대해 고민을 해 보았는데, 멘토링을 통해 웹 서버의 필요성을 느꼈을 때 선정해보는 방향으로 결정

2주차 회고

진행한 내용

  1. ncloud 세팅 및 서버 배포
  2. typeORM 및 postgres DB 연동
  3. 회원가입 기능 구현
  4. base response 설정
  5. swagger를 통해 api 문서화
  6. naver 소셜 로그인
  7. jwt를 이용해 인증

고민 및 해결과정

  1. ncloud 세팅
    1. 처음에 vpc를 선택하고 subnet을 설정한 후에 메인 서버와 DB서버를 생성했다. 이때 메인 서버에서 DB 서버에 접속이 가능하게 하기 위해 세팅을 하는 과정에서 응답이 없는 오류가 발생했다. 문제가 발생했던 부분은 acg를 새로 생성하는 부분이였는데, acg를 새로 생성하고 그걸 할당해주는 과정에서 메인 서버의 ACG에 아웃바운드 규칙을 설정하지 않아서 DB서버로 접속을 할 수 없던 것이였다. 해당 부분에 아웃바운드 규칙을 추가해준 후에 메인 서버에서 DB서버로 접속을 성공했다.
  2. 데이터베이스
    1. 데이터베이스는 postgres를 선택했다. 이유는 사용자에 따라 많은 음식점들의 정보를 가져오는 우리의 서비스 특성상 대규모 처리에 조금 더 강점을 가지는 postgres가 적절하다고 판단했다. 또한 추후 주변 음식점 검색을 위해 거리 계산이 필요할 것 같은데 이 부분에 postgres의 extension인 postGIS를 사용할 수 있지 않을까 라고 생각했다.
    2. 메인 서버에서 postgres에 접속하는 과정에서도 오류가 발생했다. 개발을 할때는 같은 pc에 postgres를 설치하고 진행했기 때문에 이 오류를 발견하지 못했는데, 메인 서버에서 실행하려고 하니 DB서버에 있는 postgres에 접속을 하지 못하는 문제가 발생했다. 문제가 발생한 이유는 postgres의 원격 접속 설정을 바꿔주지 않았기 때문이였다. postgresql.conf와 pg_hba.conf 파일에 원격접속을 허용해주도록 하는 세팅을 추가해 준 후 진행하니 문제가 해결됐다.
  3. base response
    1. 안드로이드 분야와 협업을 진행하면서, 서버측에서 주는 성공응답과 에러응답 형식이 통일되어 있어야 안드로이드 개발자가 조금 더 쉽게 응답을 사용할 수 있을 것 같다고 생각을 했다. 그래서 어떤 방법이 있을지 고민하고 찾아보다가 interceptor와 filter를 사용하면 좋을 것 같다고 판단했고, base response를 만들고 성공 응답에 대해서 global로 등록된 interceptor를 통과하도록 했고 에러응답은 globalFilter를 사용하여 동일한 형식으로 응답을 할 수 있는 구조를 만들었다.
  4. 소셜 로그인
    1. 웹 oauth는 구현을 해봤지만 안드로이드에서 oauth는 처음해봤기에, 익숙한 웹 oauth 형태로 구현을 하다보니 문제가 발생했다. 이전에 passport 모듈을 사용하지 않고 oauth를 구현해봤기 때문에 이번에는 passport 모듈을 사용해보고 싶어서 naver 용 passport-naver를 통해 진행을 했는데, 유저의 정보를 가져오지 못하고 에러페이지만 나오는 문제가 발생했다. 찾아보니 안드로이드에서 oauth를 할때는 이미 sdk를 통해 accessToken을 받아오기 때문에 서버에서는 프로필 정보를 가져오는 api를 해당 토큰과 함께 요청하기만 하면 된다고 해서, axios를 통해 naver에 api 요청을 하는 방식으로 문제를 해결했다.

3주차 회고

진행한 내용

1. refresh token 발급 및 이를 통한 access token 재발급
2. 로깅
3. 테스트
4. 음식점 정보를 받아와 저장
5. 음식점 검색 시 자동완성 기능
6. 홈 화면에서 필터 클릭 시 해당 유저의 맛집 리스트 응답 기능

고민 및 해결 과정

  1. refresh token 발급 및 이를 통한 access token 재발급
    1. access token이 만료되었을 때 refresh token을 전달받아야 하는데 이때 authorization 헤더를 써야할 지, 아니면 body를 통해 전달받아야 할지 고민했다. 이때 authorization 헤더에 대해 찾아보니 이 헤더는 access token을 전달할 때 사용하는 것이 일반적이라는 내용을 확인 했고, 결론적으로 body를 통해 refresh token을 전달받고 이를 통해 access token을 재발급 절차를 진행하기로 결정했다.
  2. 로깅
    1. 로그를 추후에 확인하기 위해서는 파일로 저장하는 것이 편리할 것 같다고 판단하여 nestjs의 default logger가 아닌 winston을 사용하여 custom logger를 만들어 사용했고, 모든 동작에 대해 공통적으로 로깅을 하는 것이 좋을 것 같다고 판단하여 interceptor를 이용하여 공통된 로그를 저장하도록 설정했다.
    2. 로깅을 할 때 어떤 정보를 표시해야 할지에 대해 고민을 했다. 처음에는 특정유저에게 요청이 오면 그 유저의 닉네임, 요청사항 등 자세한 내용을 모두 로깅을 해놔야 할 지 고민했지만, 조금 과한 것 같다는 생각이 들었고 단순하게 timestamp, 메서드, 엔드포인트, 응답 메세지, 지연시간 정도만 표시하는 것을 기본 형태로 해서 구현을 했다. 이 부분에 대해 멘토분께 질문한 결과 유저에 대해 너무 많은 정보를 로그에 남겨놓는 것은 보안 상 좋은 것은 아닌것 같고 조금 더 디버깅하기 편한 것을 원한다면 요청에 대한 body 부분을 추가하면 좋을 것 같다는 피드백을 받아 해당 부분을 추가해 볼 계획이다.
  3. 테스트
    1. DB를 테스트 할 때 테스트 DB를 따로 구축할 필요 없이 간단하게 InMemoryDB를 통해 단위 테스트를 진행하기 위해 선택했다. 이 과정에서 DB 연결을 할 때 공식 문서에 나와있는 방법과는 약간 상이해서 많은 에러를 겪었지만 결국 datasoure를 하나 만들고 초기화 및 동기화를 해준 후 테스트 모듈에 overideProvider를 통해 추가하여 연결에 성공했다.
  4. 음식점 정보를 받아와 저장
    1. 처음에는 네이버나 카카오 api를 사용할 계획을 세웠으나, 한번에 가져올 수 있는 양이 너무 적고 반복해서 api 요청을 하는것도 불가능했기 때문에 사용하기 부적절하다고 판단했다. 그래서 한번에 1000개씩 반복적으로 api 요청을 하여 모든 정보를 가져올 수 있는 서울시 공공 api를 선택했다
    2. 약 50만개의 데이터를 받아와야 하기 때문에 1000개씩 500번 정도의 api 요청을 해야하고 이 데이터를 DB에 매번 저장하는 과정에서 매우 오랜 시간(약 10분)이 걸렸다. 이 부분을 개선하기 위해 처음에는 worker 모듈을 사용하여 쓰레드를 생성하여 작업하고 저장하니 시간이 약 4분 정도로 단축됐다. 이후 조금 더 개선을 하기 위해 api를 비동기적으로 요청하고 해당 작업이 어느 정도 진행됐을 때마다 한번씩 DB에 bulk insert를 하는 방식을 시도했고 시간은 약 2분 정도로 단축됐다.
    3. 위치 정보는 postGIS 확장을 이용하여 저장을 시도했다. 이때 안드로이드쪽과 편하게 협업하기 위해서는 WGS좌표계로 저장할 필요가 있었는데, 서울시 공공 api에서 제공하는 위치 정보는 중부TM 좌표계였기 때문에 변환을 해야했다. proj4 라는 npm 모듈을 이용하여 변환을 시도했는데 위치 정보에 오차가 매우 큰 오류가 발생했고 더 정확한 보정된 중부원점 좌표계를 활용하여 WGS 좌표계로 변환하여 위치 정보를 저장했다.
  5. 음식점 검색 시 자동완성 기능
    1. 검색 자동완성 기능의 구현에 대해 어느 정도 수준으로 구현을 해야할 지에 대해 고민을 했었는데, 주어진 시간과 우리의 서비스 특성을 고려했을 때 검색엔진 정도의 고수준은 적절하지 않다고 판단하고, 단순하게 DB에서 조회를 할 때 Like문을 이용하여 검색하는 단어가 포함되는 음식점을 응답하는 방식으로 결정했다.
    2. 검색된 음식점들을 거리순으로 정렬할 필요가 있었기 때문에 현위치를 기준으로 음식점들의 거리를 어떻게 계산 할지를 고민했다. 이때 하버사인 공식을 사용하여 직접 코드를 구현할지도 생각을 했지만, postGIS 확장을 이용하여 위치 정보를 저장한 경우 DB에서 내부적으로 거리를 계산할 수 있는 방법이 있다는 것을 알게 되었고 해당 방법을 통해 우선적으로 기능을 구현한 후 추후에 직접 거리를 계산하는 것과의 시간 차이를 분석해서 더 빠른 방식을 최종적으로 채택할 예정이다.
  6. 홈 화면에서 필터 클릭 시 해당 유저의 맛집 리스트 응답 기능
    1. 내가 필터 요청을 한 유저의 맛집 리스트를 가져오는 작업은 단순했지만, 그 음식점을 내가 맛집 리스트에 등록했는지 여부도 표시해줘야 했기 때문에 쿼리가 조금 더 복잡해졌다. 이때 raw query를 쓰는것과 typeORM 문법을 사용하는 방법 중 고민하다가, typeORM을 사용했을 때 location 정보가 {x,y}의 객체로 잘 나오는 것을 확인했고 이런식으로 ORM을 사용할 때 얻는 이점을 잃을 수 있겠다고 판단하고 typeORM 문법을 이용하여 쿼리를 수행했다.

4주차 회고

진행한 내용

  1. MVP 버전 배포를 위한 api 개발
  2. Object storage 사용
  3. Docker를 활용한 테스트 환경 구축
  4. 부적절한 사용자의 토큰을 만료시키기 위한 로직 구현

기술적 고민

  1. Object Storage
    • 기술 선택의 이유
      • DB에 고화질 사진과 같은 대용량 버퍼 데이터 자체를 보관하기에는 DB의 비용 증가와, 트래픽 과부하 시 DB 서버에 더욱 부담을 줄 수 있기에 DB에는 이미지의 path( URL ) 만을 저장하고, 이후 해당 정보에 대한 요청이 들어온다면 DB에서 해당 URL을 꺼내 서버가 클라이언트에게 응답하도록 하는 것이 좋다고 판단했다.
    • 목표 설정
      • 클라이언트 사용자가 사진 등록을 할 때, 멀티 파트로 나누어진 사진 데이터 전송을 서버가 받아 이를 Object Storage에 최초 저장 후, 해당 url path를 DB에 저장하는 것.
    • 기술적 장애물
      • 서버가 최초 1 회 이미지 데이터를 받을 때, 한 번에 모두 받는 것이 아닌 멀티 파트로 나누어진 연속적 요청을 처리하는 것
    • 해결책
      • Nest.js에서 “Multer” 라이브버리의 “@UseInterceptors” 데코레이터 사용할 계획

  1. Docker Container Test
    • 기술 선택의 이유
      • 기존 단위 테스트를 pg-mem 라이브러리를 활용한 인메모리 가상 DB 테스트 방식을 고려하였고, 실제로 진행하였으나 PostgreSQL DB에서 Restaurant 테이블 컬럼 속성 중 postgis 확장에서 제공하는 point타입의 location field 를 pg-mem에서는 지원하지 않았기에 테스트 자체가 불가능했다. 때문에 postgis 확장을 지원하는 테스트 도구를 찾아야 했고 컨테이너 가상 DB test 방식인 Docker를 찾게 되었다. Docker를 단위테스트에 사용하는 것은 너무 무거워지기에 다른 테스트 도구들도 찾아 보았지만, 가상 DB 방식을 이용하면서 Postgis 확장을 지원하는 테스트 도구는 Docker가 최적이라 판단하였다.
    • 목표 설정
      • 실제 DB와 유사한 환경이지만 실제 DB는 아닌 컨테이너 가상 DB 환경에서 각 단위 테스트, 통합 테스트, 시나리오 테스트를 진행하자.
    • 기술적 장애물
      • 단위 테스트 진행 시에, Docker를 이용하기 때문에 테스트 진행 속도가 인메모리 가상 DB에 비해 비교적 느리다.
    • 해결책
      • 테스트 실행 환경을 최적화 및 병렬 테스트 실행, 컨테이너 재사용 등의 전략을 사용

  1. Refresh-Token 저장 DB
    • 선택의 이유 : 사용자 로그인 시에 생성하는 Refresh-Token값을 저장하기 위해서 메모리 DB인 Redis와 RDB인 PostgreSQL 을 고려하였다. Redis를 사용할 경우에 메모리 DB인 만큼 접근 속도가 RDB를 사용해 refresh-token을 저장할때보다 확실한 이점이 있었지만, refresh-token 하나만을 위해 사용하는 것은 비용적 측면, 그리고 확실한 이유가 되지 않는다고 판단했다. 지금 사용하는 PostgreSQL 서버는 public was 서버와 같은 vpc 내에 위치하고 있기 때문에 접근 속도도 나쁘지 않다고 판단했고, RDB를 더 활용해보자는 생각이 있어 RDB를 사용해 Refresh-token을 저장하기로 결정했다.
    • 기술적 장애물 : Redis보다 늦은 접근 속도를 개선해야 한다.
    • 해결책 :
      • PostgreSQL에서 Refresh-Token 관련 테이블의 인덱싱을 최적화
      • Refresh-Token 관련 쿼리를 분석하고 최적화

5주차 회고

진행한 내용

  1. FINAL 버전을 위한 api 개발
  2. CI/CD 설정
  3. 웹 서버 설정
  4. 부하테스트 진행

기술적 고민

  • CI / CD
    • 사용한 기술 : github action, docker
    • 기술 선택의 이유
      • github를 사용하고 있었기 때문에 특정 브랜치에 push가 됐을 때 github action을 통해 자동으로 배포가 되면 좋을 것 같다고 생각했고, 이때 기존에 실행되고 있던 was를 종료하고 다시 새로운 was를 실행시키는 과정이 환경의 제약 없이 잘 동작하도록 하기 위해서는 docker를 사용하는 것이 좋겠다고 판단했다.
    • 목표 설정
      • develop/be 브랜치에 새롭게 push가 된 경우, 새로운 코드 ncp의 vpc에서 실행되고 있는 was 서버에 자동으로 배포하는 것
  • 웹 서버
    • 사용한 기술 : nginx
    • 기술 선택의 이유 :
      • github action과 docker를 이용해서 ci/cd를 구축하는 과정에서 기존에 was에 직접 SSL 인증서를 저장하고 https 접속을 하는 과정을 변경할 필요가 있다고 판단했다. 그 결과 was docker에 매번 SSL 인증서를 넣어서 실행시키는 대신, 앞 단에 nginx를 두고 SSL 인증서를 등록한 후 리버스 프록시로 사용하여 모든 요청을 https로 수신한 후 was에 내부망을 통해 http로 요청을 전달하는 방식을 선택했다.
    • 목표 설정
      • 모든 외부 트래픽은 리버스 프록시인 nginx에 https를 통해 주고받고, was와의 통신은 nginx만 내부망을 통해 http 통신하기
  • 부하테스트
    • 사용한 기술 : Apache JMeter
    • 기술 선택의 이유
      • 부하테스트를 직접 api 호출을 여러번 해보는 형태로 하는 것 보다는 자동화 된 툴을 사용하는 것이 좋다고 판단했고, 그 중 학습 자료가 풍부하고 GUI를 통해 쉽게 사용이 가능한 JMeter를 사용해보기로 결정했다.
    • 목표 설정
      • 현재 동작 중인 was 서버의 병목 지점을 발견하고 코드 리팩토링 및 쿼리 최적화를 통한 성능 개선
Clone this wiki locally