백엔드 회의록 - boostcampwm2023/iOS04-HeatPick GitHub Wiki
클라우드 설정
버전
* docker: root@ncloud:~# docker version
Client: Docker Engine - Community
Version: 24.0.2
API version: 1.43
Go version: go1.20.4
Git commit: cb74dfc
Built: Thu May 25 21:52:13 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.2
API version: 1.43 (minimum version 1.12)
Go version: go1.20.4
Git commit: 659604f
Built: Thu May 25 21:52:13 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.21
GitCommit: 3dce8eb055cbb6872793272b4f20ed16117344f8
runc:
Version: 1.1.7
GitCommit: v1.1.7-0-g860f061
docker-init:
Version: 0.19.0
GitCommit: de40ad0
* mysql: 8.0.35
-> 도커에 mysql을 설치했었으나, 이를 취소하고 ubuntu server 위에 mysql server 설치 후 사용하는 것으로 변경
- mysql server: 5.7.42
기본 환경 설정
- cloud 서버 생성
- ssh 연결 확인
3. docker 설치 - mySQL server 설치
4. docker-mysql 이미지 설치 - mysql 컨테이너 실행
- ssh 터널링으로 mysql 접속 성공
배포 설정
- Github Action
- Docker
배포 흐름
- 패키지 설치 (npm install)
- 환경변수 파일 생성
- 도커 이미지 생성
- 도커 Hub에 업로드
- 백엔드 서버에 SSH 연결 이후 도커 Hub에서 이미지 Pull
- 가져온 이미지를 도커 컨테이너에 업로드 후 실행
트러블 슈팅
- 경로 문제: workflow에서는 항상 경로가 root로 고정
11/14
- User 테이블에서 프로필 속성을 분리하여 Profile 테이블 생성 고민
의사결정과정
RDB를 선택한 이유
SNS 시스템이기 때문에 '유저'라는 테이블과 스토리, 댓글, 프로필 이미지, 유저끼리 등 다양한 정보와 연관 관계를 맺게 된다. 따라서 이를 표현하고, 필요에 맞게 데이터를 가공하기 위해서는 RDB가 가장 적합하다고 생각하였다. 또한 데이터 스키마의 일관성을 유지하여 항상 설계된 형태의 데이터가 들어온다는 확신을 바탕으로 효과적으로 관리할 수 있다고 판단하였다.
TypeORM을 선택한 이유
ORM을 사용하지 않고 SQL 쿼리를 직접 작성할 경우 데이터의 column명을 모두 기록하여야 하고, 실수로 속성을 누락할 경우 에러가 발생하기 때문에 코드 작성 과정에서 이와 관련한 디버깅에 시간을 많이 사용하게 되는 문제점이 있었다. 이번 프로젝트에서는 코드 레벨의 로직에 더 집중할 수 있는 환경을 조성하고자 ORM을 사용하였고, Nest와의 호환성이 좋은 TypeORM을 선택하였다. 또한 이번 프로젝트는 SNS기반 프로그램이기에 사용자와 다른 객체들(게시물, 댓글, 팔로우)등과 연관 관계가 많이 존재하고, 업데이트마다 연결된 테이블들이 모두 동기화되어야 하기에 ORM의 cascading 기능을 효율적으로 사용할 수 있을 것이라 생각하였다.
다만 ORM의 경우 쿼리를 자세하게 확인할 수 없고, 복잡한 쿼리를 처리하는데 적합하지 않다는 단점이 있지만, 이번 프로젝트에서는 디버깅에 큰 문제가 되거나 ORM으로 처리하기 힘들 만큼의 복잡한 쿼리를 사용하지 않을 것으로 예상되어 trade-off를 고려했을 때 ORM을 사용하는 것이 효율적일 것이라 판단하였다.
docker를 선택한 이유
배포 과정에서 '다른 환경'은 매우 큰 변수로 작용한다. docker는 local에서 테스트했던 환경과 동일한 환경으로 image를 생성하고 이를 컨테이너에 올려 실행시킬 수 있기 때문에 배포 과정에서 예상하지 못한 변수로 인한 에러를 최소화할 수 있을 것이라 생각하여 선택하였다.
github action을 선택한 이유
jenkins와 github action중 어떤 tool을 사용할지 고민하였는데, 6주간의 짧은 프로젝트 기간을 효율적으로 사용하기 위하여 러닝커브가 비교적 가벼운 github action을 선택하였다.
DB서버와 서비스 서버를 분리한 이유
DB서버와 서비스 서버를 분리하는 이유는 메모리 공간 관리 과정에서, db서버가 쉽게 종료될 수 있기 때문이다. 두 서버를 같이 운영하는 상황에서 많은 API 요청이 들어와 메모리 공간이 부족해지는 경우 OOM killer는 일반적인 상황에서 가장 많은 메모리 공간을 차지하는 DBMS 프로세스를 종료시킨다. 이로 인해 DB서버가 다운될 수 있기 때문에, 두 서버를 분리하였다.
자동완성을 위한 Trie 구조를 제거하고, naver cloud search로 대체한 이유
기존에는 20~30개 정도의 검색 기록만을 가지고 Trie구조를 생성하고, 이를 1시간마다 동기화하는 방식으로 검색 자동 완성 기능을 구현하였다. 하지만 Trie 구조가 커질수록 메모리에 주는 부담이 늘어나는 것에 대한 고민이 생겼고, 해당 구조를 다른 공간에서 대신해서 들고 있을 수 있다면 문제 개선에 도움이 될 것이라 생각하였다. 따라서 Trie 구조를 사용하던 자동완성/스토리캐싱 기능 중에 자동완성 기능은 naver cloud search로 대체하였다.
기술적 도전
배포 및 자동화
개발을 진행하는 과정에서 iOS-server 간 개발속도의 차이로 인한 불편함을 체감하였습니다. 따라서 서버의 개발이 완료되는 즉시 iOS측에서 테스트할 수 있는 환경을 마련하는 것이 필요하다고 생각하였고 배포 및 자동화를 최우선 순위로 설정하여 진행하였습니다.
ORM
ORM은 쿼리를 직접 작성할 필요가 없어서 분명 편리하지만 동작 방식에 대해 분명히 이해하고 있어야 효과적으로 사용할 수 있었습니다. 프로젝트를 진행하면서, ORM 내에서 Transaction을 직접 제어할 필요가 있었고, Cascade 속성을 통해 부모의 상태 변화에 따른 자식의 영향, lazy-loading을 이용해 발생한 N+1문제와 성능 개선, 복잡한 쿼리를 제어하기 위해 사용한 Query Builder도 수행해 보았습니다.
푸시 서비스
저희 프로젝트에서 알림 서비스를 도입을 위해 Firebase Cloud Messaging을 사용했고 푸시 요청을 책임지는 푸시 서버를 만들어 서비스 했습니다. 푸시 서버를 만들면서 푸시 요청의 흐름에 대해 학습할 수 있었고, 별도로 Message Queue 패턴을 학습하고 실제 적용해보며, 비동기적으로 작업하고, 시스템 장애에 따른 메세지 소실이 없도록 하기 위해서 해당 프로젝트에 적용하여 수행하였습니다.