명절 잔소리 영수증

기간  — 

명절 잔소리로 쌓이는 젊은 세대의 스트레스와 세대 간 단절에서 출발했습니다. 잔소리와 대처 경험을 공유하고, 영수증과 통계로 만들어 나눕니다. 300명 이상이 참여한 사전 설문으로 명절 스트레스 데이터를 모았고, SSAFY 공통 프로젝트 1위로 우수상을 받았습니다. 백엔드 작업의 절반 이상을 맡았고, 서버와 CI/CD는 혼자 구축했습니다.

설 연휴 동안 회원 약 200명

Java 17Spring Boot 3.2.1Spring Data JPASpring SecuritySpring BatchQuerydslMySQL 8.0.35Redis 7.2AWS EC2GitLab RunnerDocker

  • 잔소리⁠·⁠대처 경험 피드
  • 좋아요⁠·⁠팔로우
  • 영수증 출력과 공유
  • 마이페이지
  • 통계 데이터 시각화
  • GitLab Runner와 Docker Hub를 거친 EC2 배포
  • Nginx 뒤 Next.js⁠·⁠Spring Boot⁠·⁠MySQL⁠·⁠Redis 컨테이너 구성

Redis에서 팔로우 양방향 관계

문제
비관계형 저장소에서 팔로우 관계를 표현해야 했습니다. 단방향인 좋아요와 달리 팔로우는 N:M 관계라, 양방향 저장은 적재량이 늘고 단방향 저장은 조회가 느려집니다.
해결
와일드카드 탐색은 최대한 피해야 한다고 보고 양방향 저장을 택했습니다. 키에 following⁠·⁠follower 접두사를 붙여 한 사용자의 팔로잉과 팔로워를 각각 바로 읽게 했습니다. 팔로워⁠·⁠팔로잉 목록을 조회할 때 탐색 시간을 크게 줄였습니다.

배치 동기화의 전체 탐색

문제
매일 Redis 데이터를 MySQL로 옮기는 배치가 전체 데이터를 훑었습니다. 바뀐 데이터만 옮길 방법이 필요했습니다.
해결
변경된 데이터의 키를 Redis 정렬 집합에 넣고, 점수를 갱신 시각으로 두었습니다. 배치는 직전 배치 이후 바뀐 데이터만 MySQL에 반영하고, 끝나면 변경 목록을 비워 다음 변경만 담게 했습니다. 좋아요⁠·⁠팔로우 취소로 값이 빈 항목은 테이블에서도 지워 공간을 확보했습니다.

가격 상위 5개 영수증

문제
최종 통계에 가격이 가장 높은 영수증 5개가 필요했습니다. 모든 사용자의 모든 영수증에서 한 번에 찾으면 비용이 컸습니다.
해결
배치 동기화에 쓴 정렬 집합 방식을 응용했습니다. 영수증을 저장할 때 가격을 점수로 상위 5개 집합에 넣고, 6위로 밀린 영수증은 바로 지웠습니다. 한 시점에 몰리던 탐색 부하를 저장 시점으로 나눠, 전체 영수증을 한꺼번에 검색하지 않게 했습니다.

한 저장소에서 프론트엔드⁠·⁠백엔드 배포

문제
프로젝트에서 저장소를 하나만 쓸 수 있어, 프론트엔드와 백엔드 코드를 한 저장소에 두고 배포를 자동화해야 했습니다. Jenkins는 초기 설정 비용이 크고 서버에서 차지하는 자원도 많았습니다.
해결
플러그인은 적지만 가볍게 파이프라인을 꾸릴 수 있는 GitLab Runner를 택했습니다. 파이프라인은 환경변수 파일 생성, 빌드, Docker 이미지 빌드⁠·⁠Docker Hub 업로드, 서버 실행의 4단계로 나눴습니다. DB 접속 정보 같은 비밀 값은 GitLab CI/CD 변수에 두고 배포 때 .env 파일로 만들었습니다.

아키텍처

GitLab Runner, Docker Hub, EC2 위의 Nginx, Next.js, Spring Boot, MySQL, Redis를 보여 주는 명절 잔소리 영수증 배포 구조도
GitLab Runner로 이미지를 빌드해 EC2의 Docker 컨테이너로 배포하는 구성입니다.
명절 잔소리 영수증 시작 화면을 담은 기울어진 스마트폰 목업
명절 잔소리 영수증 시작 화면을 담은 스마트폰 목업
해시태그 필터가 있는 잔소리 목록 화면
영수증 발급 버튼과 저장된 잔소리 카드 화면
QR 코드가 포함된 잔소리 영수증 출력 화면
평균 잔소리 횟수와 성별 통계 화면