개인 개발 리포트 - 덕후감 프로젝트
개인 개발 리포트 - 덕후감 프로젝트
1. 프로젝트 개요
덕후감은 ISBN 정보를 기반으로 책 정보를 자동으로 불러오고, 사용자들이 책에 대한 리뷰와 감상을 나눌 수 있는 책 커뮤니티 플랫폼입니다.
이번 프로젝트는 단순한 CRUD 서비스 구현을 넘어, 도서 검색, 리뷰, 댓글, 알림, 대시보드, 로그 관리, 배포 자동화까지 포함한 백엔드 중심의 팀 프로젝트였습니다.
프로젝트 목표
- ISBN 기반 도서 정보 조회 및 등록
- 사용자 리뷰 및 댓글 기능 제공
- 기간별 인기 도서, 인기 리뷰, 파워 유저 대시보드 제공
- AWS 기반 배포 환경 구성
- CloudWatch 및 S3를 활용한 로그 적재 구조 구성
- CI/CD 파이프라인을 통한 배포 자동화
핵심 기능
▶ 핵심 기능 전체 보기
사용자 관리
- 사용자 정보
- 사용자 등록 / 회원가입
- 사용자 수정
- 사용자 삭제
- 로그인
도서 관리
- 도서 정보
- 도서 등록
- 도서 수정
- 도서 삭제
- 도서 목록 조회
- ISBN 기반 도서 정보 조회
리뷰 관리
- 리뷰 정보
- 리뷰 등록
- 리뷰 수정
- 리뷰 삭제
- 리뷰 목록 조회
댓글 관리
- 댓글 정보
- 댓글 등록
- 댓글 수정
- 댓글 삭제
- 댓글 목록 조회
대시보드 관리
- 인기 도서
- 인기 리뷰
- 파워 유저
알림 관리
- 알림 정보
- 알림 등록
- 알림 수정
- 알림 삭제
- 알림 목록 조회
기술 요구 사항
- 유효성 검사
- 커스텀 예외
- 로그 관리
- 테스트 주도 개발
- CI/CD 파이프라인 구축
2. 담당한 작업
이번 프로젝트에서 제가 주로 담당한 작업은 다음과 같습니다.
- 대시보드 기능 구현
- AWS 수동 배포 및 자동 배포 환경 구성
- AWS Lambda를 활용한 CloudWatch 로그의 S3 적재 자동화
- Docker 및 Docker Compose 환경 구성
- Jacoco 테스트 커버리지 리포트 활성화 및 코드 커버리지 확인 환경 구성
- 발표 PPT 전체 구성 및 수정
- 배포 과정에서 발생한 AWS 관련 이슈 해결
특히 대시보드 기능과 AWS 배포 환경 구성에서 많은 시간을 사용했습니다.
기능 구현뿐 아니라 실제 서버 배포 과정에서 발생하는 포트, 태스크, 헬스체크, 리소스 문제를 직접 겪으며 인프라 구조를 더 구체적으로 이해하게 되었습니다.
3. 기술적 성과
사용한 기술 스택
- Java
- Spring Boot
- Spring Data JPA
- H2 Database
- PostgreSQL
- Docker
- Docker Compose
- AWS EC2
- AWS ECS
- AWS CloudWatch
- AWS Lambda
- AWS EventBridge
- AWS S3
- DataGrip
구현한 주요 기능
대시보드 기능
이번 프로젝트에서 담당한 핵심 기능은 대시보드 파트였습니다.
대시보드는 서비스 내 활동 데이터를 기반으로 기간별 랭킹을 제공하는 기능입니다.
▶ 대시보드 기능 상세 보기
인기 도서
기간별 인기 도서 순위를 계산합니다.
- 일간
- 주간
- 월간
- 역대
인기 도서 점수는 해당 기간의 리뷰 수와 평점을 기준으로 계산합니다.
예시:
점수 = (해당 기간의 리뷰 수 * 0.4) + (해당 기간의 평점 평균 * 0.6)
인기 리뷰
기간별 인기 리뷰 순위를 계산합니다.
- 일간
- 주간
- 월간
- 역대
인기 리뷰 점수는 해당 기간의 좋아요 수와 댓글 수를 기준으로 계산합니다.
예시:
점수 = (해당 기간의 좋아요 수 * 0.3) + (해당 기간의 댓글 수 * 0.7)
파워 유저
기간별 활동 점수에 따른 파워 유저 순위를 계산합니다.
- 일간
- 주간
- 월간
- 역대
파워 유저의 활동 점수는 사용자가 작성한 리뷰의 인기 점수, 참여한 좋아요 수, 댓글 수를 기준으로 계산합니다.
예시:
활동 점수 = (작성한 리뷰의 인기 점수 * 0.5)
+ (참여한 좋아요 수 * 0.2)
+ (참여한 댓글 수 * 0.3)
4. 문제점 및 해결 과정
이번 프로젝트에서 가장 많은 시간을 사용한 부분은 AWS 배포 과정이었습니다.
특히 “무중단 배포”라는 개념을 처음부터 정확히 이해하지 못한 상태에서 현재 인프라 환경에 맞지 않는 설정을 적용하려다 보니, 포트 충돌, 헬스체크 실패, 메모리 부족, 로드밸런서 설정 실패 등이 연쇄적으로 발생했습니다.
핵심 문제는 개별 에러 하나가 아니라, 현재 인프라 구조와 맞지 않는 배포 전략을 적용하려 했던 것이었습니다.
AWS 배포 트러블슈팅
▶ AWS 배포 트러블슈팅 상세 보기
문제 발생 배경
최소 비용으로 배포 환경을 구성하기 위해 다음과 같은 조건으로 시작했습니다.
- 단일 EC2 인스턴스
- t3.micro
- ECS Task 1개
- bridge 네트워킹
- Rolling Update 설정
- 최소 100%
- 최대 200%
- Host Port 80 고정
- Container Port 8080
처음에는 무중단 배포를 구현하기 위해 기존 Task와 신규 Task가 동시에 실행되는 구조를 기대했습니다.
하지만 단일 EC2 환경에서 Host Port를 80으로 고정해둔 상태였기 때문에, 새 Task가 동시에 실행되면서 같은 포트를 사용하려고 했고, 이로 인해 포트 충돌이 발생했습니다.
발생한 문제
1. RESOURCE:PORTS 오류
TaskFailedToStart: RESOURCE:PORTS
컨테이너는 8080 포트를 사용하고 있었고, 이를 EC2 호스트의 80번 포트에 고정으로 연결해 둔 상태였습니다.
기존 Task가 이미 Host Port 80을 사용하고 있는데, Rolling Update 과정에서 새로운 Task도 같은 Host Port 80을 사용하려 하면서 충돌이 발생했습니다.
2. OOM 오판 및 리소스 변경
처음에는 배포가 무한 대기 상태에 빠지고 롤백되는 현상을 OOM 문제로 오판했습니다.
그래서 t3.micro에서 t3.small로 변경하고, Task 리소스도 조정했습니다.
초기 설정:
0.25 vCPU / 0.5 GB Memory
Host Port: 80
Container Port: 8080
변경 후 설정:
0.5 vCPU / 0.75 GB Memory
Host Port: 8080
Container Port: 8080
리소스를 상향하면서 일부 안정성은 개선되었지만, 근본 원인은 리소스 부족이 아니라 배포 전략과 포트 설정의 불일치였습니다.
3. 헬스체크 및 Exit Code 143 문제
Dockerfile에서 실행 환경으로 amazoncorretto:17-alpine을 사용하고 있었습니다.
FROM amazoncorretto:17-alpine
WORKDIR /app
배포 테스트 중 헬스체크 과정에서 Exit Code 143 문제가 반복적으로 발생했습니다.
이 과정에서 헬스체크 명령이 현재 실행 환경과 안정적으로 맞지 않는다고 판단했고, 불안정한 헬스체크 명령을 제거하는 방식으로 대응했습니다.
4. 로드밸런서 도입 실패
무중단 배포를 위해 Load Balancer와 Target Group을 도입하려고 했습니다.
하지만 헬스체크 실패가 반복되면서 신규 Task가 정상 상태로 판단되지 않았고, 결국 배포가 계속 실패했습니다.
최종적으로는 현재 프로젝트의 비용과 인프라 규모를 고려해 Load Balancer 도입을 제외하고, 기존 방식으로 롤백했습니다.
최종 해결
최종적으로 다음과 같이 설정을 정리했습니다.
- Rolling Update 비율 수정
- 최소 0%
- 최대 100%
- Host Port를 8080으로 조정
- 보안 그룹 인바운드 규칙 수정
- 불안정한 헬스체크 명령 제거
- Load Balancer 도입 보류
- 현재 환경에 맞는 단순한 배포 방식 선택
이를 통해 최종적으로 서버 배포에 성공했습니다.
배운 점
이번 문제를 해결하면서 무중단 배포는 단순히 기존 서버와 새 서버를 동시에 띄우는 것이 아니라는 점을 배웠습니다.
무중단 배포를 위해서는 다음과 같은 조건을 함께 고려해야 합니다.
- 네트워크 모드
- Host Port 할당 방식
- Task 개수
- Load Balancer 사용 여부
- Health Check 설정
- 배포 전략
- 인스턴스 리소스
- 보안 그룹 설정
특히 단일 EC2와 고정 Host Port 구조에서는 Rolling Update로 신규 Task를 동시에 실행하는 것이 포트 충돌을 만들 수 있다는 점을 직접 경험했습니다.
지원님, 재훈님과 함께 약 10시간 이상 문제를 분석하고 검색하며 해결했고, 이 과정을 통해 ECS, Task, Port Mapping, Health Check, Load Balancer 개념을 이전보다 훨씬 구체적으로 이해하게 되었습니다.
5. 협업 및 피드백
이전에는 개인적으로 궁금한 점이나 문제가 생겨도 팀원들이 바빠 보이면 질문하기를 망설였습니다.
그래서 혼자 고민하다가 시간을 많이 쓰는 경우가 있었습니다.
이번 프로젝트에서는 이전과 다르게 훨씬 적극적으로 질문하고 소통했습니다.
특히 AWS 배포 문제를 해결하는 과정에서 팀원들과 함께 원인을 찾고, 서로 검색한 내용을 공유하고, 가능한 해결 방법을 하나씩 시도했습니다.
이 과정에서 단순히 기능을 구현하는 것보다, 함께 문제를 분석하고 해결하는 협업 과정이 매우 중요하다는 것을 느꼈습니다.
협업을 통해 느낀 점
- 혼자 오래 고민하는 것보다 빠르게 공유하는 것이 더 효율적이었다.
- 같은 문제를 여러 관점에서 보면 원인을 더 빨리 좁힐 수 있었다.
- 팀원들과 함께 해결한 문제는 더 오래 기억에 남았다.
- 기술적인 문제 해결뿐 아니라 커뮤니케이션 능력도 개발 역량의 중요한 부분이라는 것을 느꼈다.
6. 코드 품질 및 최적화
대시보드 기능을 구현하면서 코드의 가독성과 유지보수성을 고려하려고 했습니다.
특히 기간별 랭킹 데이터를 다루는 기능이기 때문에, 단순히 동작하는 코드보다 이후 확장과 조회 성능을 함께 고려하는 것이 중요했습니다.
가독성과 유지보수성
PeriodType enum 사용
대시보드 기간 조건은 문자열을 직접 비교하지 않고 PeriodType enum으로 관리했습니다.
DAILY,
WEEKLY,
MONTHLY,
ALL_TIME
이렇게 허용 가능한 기간 값을 enum으로 제한하면, 문자열 오타나 잘못된 값으로 인한 오류 가능성을 줄일 수 있습니다.
예를 들어 "daily", "Daily", "DAILY"처럼 문자열을 직접 비교하면 대소문자나 오타로 인한 문제가 발생할 수 있습니다.
반면 enum을 사용하면 코드에서 사용할 수 있는 값이 명확하게 제한되기 때문에 안정성이 높아집니다.
대시보드 전용 엔티티 분리
인기 도서와 파워 유저 데이터를 각각 PopularBook, PowerUser 엔티티로 분리했습니다.
일반 도서나 사용자 테이블에 통계 필드를 섞지 않고, 대시보드 전용 테이블을 따로 두었기 때문에 각 도메인의 책임이 비교적 명확하게 나뉩니다.
Book
→ 도서 자체의 정보 관리
User
→ 사용자 자체의 정보 관리
PopularBook
→ 인기 도서 랭킹 관리
PowerUser
→ 파워 유저 랭킹 관리
이 구조는 이후 대시보드 집계 방식이 변경되거나 새로운 랭킹 기능이 추가될 때도 기존 도메인에 미치는 영향을 줄일 수 있습니다.
기능별 API 분리
조회 API도 기능별로 분리했습니다.
인기 도서 조회
GET /api/books/popular
파워 유저 조회
GET /api/users/power
각 API는 period, cursor, after, limit 값을 받아 커서 기반 페이지네이션 형태로 응답하도록 구성했습니다.
성능 최적화
인덱스 설정
대시보드 엔티티에는 조회 성능을 고려한 인덱스를 설정했습니다.
period, base_date, ranking
인기 도서와 파워 유저는 모두 특정 기간의 최신 랭킹 데이터를 조회하는 구조이기 때문에, period, base_date, ranking 조합은 조회 성능에 중요한 기준이 됩니다.
예를 들어 사용자가 일간 인기 도서를 조회하면 다음 조건이 필요합니다.
period = DAILY
base_date = 최신 기준일
ranking 순 정렬
따라서 이 조합에 인덱스를 설정하면 특정 기간의 랭킹 데이터를 더 효율적으로 조회할 수 있습니다.
개선할 수 있는 부분
▶ 코드 품질 및 최적화 개선점 보기
현재 일부 조회 로직은 Java Stream 필터링에 의존하고 있습니다.
데이터가 적을 때는 문제가 크지 않지만, 대시보드 집계 데이터가 계속 쌓이면 불필요하게 많은 데이터를 메모리에 올릴 수 있습니다.
예를 들어 모든 데이터를 조회한 뒤 애플리케이션에서 period, baseDate, cursor 조건으로 필터링하는 방식은 데이터가 증가할수록 비효율적일 수 있습니다.
개선 방향은 Repository 쿼리 메서드나 QueryDSL을 사용해 DB 단계에서 조건을 처리하는 것입니다.
예시:
findByPeriodAndBaseDateAndRankingGreaterThanOrderByRankingAsc(...)
이렇게 개선하면 애플리케이션에서 전체 데이터를 필터링하지 않고, DB에서 필요한 랭킹 데이터만 가져올 수 있습니다.
결과적으로 다음과 같은 장점이 있습니다.
- 메모리 사용량 감소
- 조회 성능 개선
- 데이터 증가에 대한 안정성 향상
- 서비스 로직 단순화
7. 향후 개선 사항 및 제안
이번 프로젝트를 진행하면서 기능 구현뿐 아니라 배포, 로그 관리, 협업 방식까지 많은 부분을 경험할 수 있었습니다.
다만 프로젝트 기간과 비용 문제로 인해 충분히 고도화하지 못한 부분도 있었습니다.
AWS 인프라 권한 및 가시성 개선
AWS 인프라를 일부 팀원만 직접 확인할 수 있었기 때문에, 배포 이슈가 발생했을 때 모든 팀원이 같은 화면을 보면서 분석하기는 어려웠습니다.
향후에는 각 팀원에게 필요한 범위 내에서 AWS 리소스를 확인할 수 있는 권한을 부여하면 좋을 것 같습니다.
이를 통해 다음과 같은 개선을 기대할 수 있습니다.
- 배포 문제 발생 시 빠른 원인 공유
- CloudWatch 로그 확인 효율 향상
- ECS Task 상태 확인 가능
- 팀원 간 인프라 이해도 향상
대시보드 조회 로직 고도화
현재 일부 대시보드 조회 로직은 Java Stream 필터링에 의존하고 있습니다.
데이터가 증가할 경우 DB 쿼리 기반 조회로 개선할 필요가 있습니다.
향후에는 다음과 같은 방식으로 개선할 수 있습니다.
- Repository 쿼리 메서드 추가
- QueryDSL 기반 동적 조회 적용
- Batch 집계 로직 최적화
- 랭킹 데이터 보관 기간 정책 추가
- 오래된 통계 데이터 아카이빙
배포 구조 개선
이번 프로젝트에서는 비용과 시간 문제로 Load Balancer 기반의 완전한 무중단 배포를 끝까지 적용하지는 못했습니다.
향후에는 다음과 같은 방향으로 개선해보고 싶습니다.
- Load Balancer 기반 무중단 배포 재도전
- Health Check 경로 명확화
- ECS 네트워크 모드 개선
- Host Port 동적 할당 또는 awsvpc 방식 검토
- Blue/Green 배포 방식 학습 및 적용
마무리
이번 프로젝트는 단순히 기능을 구현하는 것보다, 실제 배포 환경에서 발생하는 문제를 해결하는 과정이 훨씬 더 기억에 남는 프로젝트였습니다.
대시보드 기능을 구현하면서는 데이터 집계, 랭킹 계산, 페이지네이션, 인덱스 설계에 대해 고민할 수 있었습니다.
AWS 배포 과정에서는 ECS, Task, Port Mapping, Health Check, CloudWatch, S3, Lambda, EventBridge 같은 개념을 실제 문제 상황 속에서 학습할 수 있었습니다.
특히 혼자 해결하기 어려운 문제를 팀원들과 함께 분석하고 해결하면서, 개발 프로젝트에서 협업과 소통이 얼마나 중요한지도 다시 느꼈습니다.
아직 부족한 부분은 많지만, 이번 프로젝트를 통해 백엔드 개발뿐 아니라 인프라와 운영 관점까지 조금 더 넓게 바라볼 수 있게 되었습니다.