백엔드 개발자로서 좋은 포트폴리오기도 하지만 제가 사랑하는 저의 축구 동아리가 더 성장하고 원활하게 진행되었으면 하는 마음이 더 크게 담겨 있는 프로젝트입니다, 이 프로젝트 덕분에 부원을 두배로 늘릴 수 있었고 경기들도 더 원활하게 진행되고 있습니다
- 기존에 모든 회원 경고, 회비 납부, 출석 여부등을 엑셀로 수기 기록하고 있었음
- 이로 인하여 약 60명 정도 이상의 인원을 동아리가 감당하기 힘들어짐
- 매 기수마다 200개가 넘는 지원서가 들어오지만 매번 죄송하게 20명 이상 뽑기가 어려웠음
- 인스타그램 디엠으로 꼭 함께하고 싶다는 많은 연락들을 본 뒤 자동화 함으로써 동아리 규모를 키우기로 결정
- 기존 부원들도 출석, 회비 납부, 패널티에 관한 인식이 줄어들고 전체적인 탄력이 늘어지게 됨
- 이로 인하여 약 60명 정도 이상의 인원을 동아리가 감당하기 힘들어짐
- 모든 회원 경고, 회비 납부, 출석 여부등을 홈페이지에서 확인하고 관리할 수 있게 됨
- 회원 경고는 경기가 끝나는 시간에 회원의 출석, 납부 여부에 따라 일괄적으로 경고를 부여하도록 배치 처리
- 회비 납부는 부원이 회비를 납부 한 뒤 확인 요청을 하면 관리자가 입금내역을 확인하여 회비 납부 확인 처리함
- 출석 여부는 Naver map api를 연동하여 매 경기 장소의 위,경도를 저장하고 출석하는 사용자의 현재 위경도와 비교하여 400m내에서만 출석이 가능하도록 개발
- 위 내용처럼 모두 자동화 된 후의 성과
- 홈페이지 베타 테스트 기수 (3기) 부터 인원을 100명까지 증원하였고 문제 없이 운영되어짐
- 4기부터 100% 도입하여 동아리 전체 부원을 약 150명까지 증원할 예정
- 운영진들의 역할에 대한 부담이 적어졌고 자동화된 패널티 처리로 긴장하게 되어 동아리의 전체적인 탄력이 살아남
- 회원 관리
- 회원가입
- 로그인
- JWT와 Spring Security의 필터를 이용한 로그인 방식
- 프로젝트 특성상 체류시간이 길지 않을것으로 예상되어 재발급 토큰은 사용하지 않음
- 회원 경기 출석 현황 (어드민)
- 관리자가 차트를 이용하여 전체 경기 참여 비율과 출석 현황 비율을 확인함
- 백엔드에서 데이터를 보내주면 프론트에서 HighCharts 라이브러리를 이용해 파싱
- 관리자가 차트를 이용하여 전체 경기 참여 비율과 출석 현황 비율을 확인함
- 경고 부여 시스템
- 매주 일요일 경기가 있는지 확인하고 있다면 경기 종료시간에 실행되는 일회용 동적 스케줄러를 생성
- 생성된 일회용 스케줄러는 해당 경기의 참여 멤버들을 조회 및 출석 & 납부 현황에 맞게 경고 부여
- 매주 일요일 경기가 있는지 확인하고 있다면 경기 종료시간에 실행되는 일회용 동적 스케줄러를 생성
- 경기 관리
- 경기 생성, 수정, 삭제, 조회 (어드민)
- 경기 생성 시 주소를 기반으로 naver map api에서 위,경도를 조회하여 같이 저장함
- 출석 확인 (사용자)
- 현재 요청자의 위경도와 경기장의 위경도를 비교하여 400m 내에서 출석되도록 개발
- 회비 납부 요청 (사용자)
- 회비 납부 후 관리자에게 회비 납부 확인 요청을 함, 회비 납부 확인 대기 상태가 됨
- 회비 납부 확인 (어드민)
- 대기중인 회비 납부 확인 요청들을 승인시켜줌, 회비 납부 완료 상태가 됨
- 참여 인원 추가, 삭제, 조회 (어드민)
- 경기에 참여하는 인원들을 추가, 삭제, 조회 하는 기능
- N:N 관계가 되는 테이블을 N:1 1:N의 사이 역할을 해주는 Member_Event 테이블로 풀어나감
- 경기 생성, 수정, 삭제, 조회 (어드민)
- Vultr 클라우드 사용
- 기존에 AWS로 배포하였지만 최소 1년 이상 서비스가 지속되어야 한다는 이유 때문에 비용이 저렴한 vultr로 이전
- 스프링 서버 1vCpu, Ram 1GB 두개
- DB 서버 1vCpu, Ram 1GB 한개
- Pinpoint 서버 2vCpu, Ram 8GB 한개
- 프로메테우스 & 그라파나 서버 1vCpu, Ram 1GB 한개
- 로드밸런싱 서버 1vCpu, Ram 1GB 한개
- !! 로드밸런서와 DB 서버는 vultr에서 제공하는 sass 형태가 아닌 직접 우분투 인스턴스에 올려서 구성
- AWS를 사용하게 되면 편한 sass를 많이 이용할것으로 예상되어 이번 프로젝트는 직접 모두 구성하고 싶었습니다
- 비용도 직접 설정하고 구성하는것이 훨씬 저렴했습니다
- Vultr Firewall 사용
- 방화벽 개발 경험을 통해 각 인스턴스들은 예상된 출발지 IP로부터 최소 필요한 포트 연결만 허용하도록 구성하였습니다
- 프론트 PHP 서버는 VPC 안에 구성하지 않았습니다, 백엔드 도메인을 통해 요청합니다.
- Nginx를 이용해서 직접 로드밸런서 인스턴스를 구성하여 엔드포인트로 사용
- Sectigo DV급 SSL 인증서를 발급받아서 도메인에 적용했습니다.
- 세션 의존적인 설계가 아니여서 베이직한 Round-Robin 방식으로 부하분산 처리했습니다.
- 추후 진행된 성능 테스트에서 로드밸런싱 후 스케일 아웃된 만큼의 TPS 개선이 있었습니다.
- 백엔드 서버는 최소 사양 인스턴스 두개로 구성
- 성능 테스트 시 4개까지 사용했지만 불필요하다고 생각되어 2개로 줄이게 되었습니다
- 최종 도커로 배포되는 방식이여서 스케일 아웃시 도커만 pull 받아 실행하면 되도록 세팅하였습니다.
- DB 서버는 최소 사양 인스턴스 한개로 구성
- Pinpoint로 트랜잭션을 분석할때 병목지점이 DB가 아닌 웹서버인것으로 확인하여 DB는 한개만 사용했습니다
- Replication DB 구성 등 여러 개선 방법들을 고려해봤지만 비용투자가 더 이상 필요 없다고 판단되었습니다
- Pinpoint 서버는 2vCpu, Ram 8GB 한개로 구성
- 최대한 낮은 사양으로 타협하고 싶었지만 일정 사양 이하의 인스턴스에서는 여러 하드웨어 자원적 문제가 발생했습니다
- 구성하던 도중 pinpoint-docker의 간단한 스크립트 문법 오류를 발견하여 PR을 보냈고 다음날에 merge 되었습니다
- 자세한 트랜잭션 추적 기능으로 추후 성능 개선의 방향성을 잡는것에 많은 도움이 되었습니다.
- 프로메테우스 & 그라파나 서버는 최소 사양 인스턴스 한개로 구성
- Spring Boot 메트릭 모니터링으로 http 요청들과 JVM에 대한 모니터링을 하였습니다
- Mysql 메트릭 모니터링으로 QPS나 SlowQuery 등을 모니터링 하였습니다.
- 시스템 자원 메트릭 모니터링으로 자원 사용량을 모니터링 하였습니다.
- 자원 사용량 및 슬로우쿼리 발생등 이상 상황시 slack webhook으로 알림을 보내도록 설정하였습니다.
실제 사용자들이 사용하는 보편적인 시나리오로 성능 테스트를 진행하였습니다.
- 200명의 유저가 동시에 회원가입 후 로그인 요청을 합니다.
- 로그인 요청 후 발급받은 JWT를 이용해 간단한 경기 목록 조회 및 경기 상세 조회를 합니다
병목 구간을 찾지 위해서 Naver의 PinPoint APM을 사용하였다, http 요청 트랜잭션을 분석했을때는 크게 병목 구간이 없었다. 그래서 전체적인 flow에 집중하였고 단순 많은 가상 유저수에 비해 서버 하드웨어 성능이 빈약함으로 인해 웹 어플리케이션 서버에서 병목 지점이 발생하고 있다고 판단하였다.
별도의 nginx 인스턴스를 개설하여 LoadBalancer로 사용했다, 요청은 Round-Robin 방식으로 분배하였다.
왼쪽이 로드밸런싱 전 TPS 측정 결과이고 오른쪽이 두대로 로드밸런싱 한 후 TPS를 측정한 결과다.
TPS 개선 정도는 스케일 아웃한 인스턴스 갯수에 정비례 하다는 사실을 확인할 수 있었다.
제일 기억에 남았던 이슈는 회원가입 중복 확인에 대한 동시성 문제였습니다
https://archanwriteup.tistory.com/entry/슬축생-프로젝트-10-회원가입-아이디-중복-확인시-동시성-문제-해결
nGrinder로 성능 테스트를 진행하던 중 의도치 않게 중복 가입 요청들이 생성되었는데 가입이 되어버렸습니다.
원인은 회원 중복 확인시 데이터베이스에 select 쿼리로 회원 id의 존재 여부를 확인하는데 별 다른 동시성 처리가 없었기 때문에 동시에 select 쿼리를 실행하고 없다는 결과를 받을 시 회원가입이 되었다는것이 문제였습니다.
처음에는 비관적 락을 이용하여 동시성 문제를 해결하였지만 이로 인해 중복회원 가입 시 API 응답 지연이 발생하여 회원 아이디에 유니크를 설정해주고 유니크 예외를 핸들링하는 방식으로 처리했습니다. 사실 처음부터 유니크 제약조건을 걸어야하는건 당연했는데 급하게 진행되느라 놓친것 같습니다..