Skip to content

Seongjun-Kwon/seat-view-reviews

Repository files navigation


개요

목적

야구 경기 관람 시 안전 펜스나 구조물에 의해 시야를 방해 받는 좌석들이 있습니다. 좌석 예매 시 좌석에서 보이는 시야를 알 수 없어 직접 후기를 찾아야하고, 찾아도 나오지 않는 경우가 많아 불편함을 느꼈습니다. 그래서, 좌석의 시야 및 후기를 모아주고 서로 공유할 수 있는 서비스를 개발하였습니다.

기간

2023.04.21 ~ 진행 중

사용 기술

  • Java17
  • Spring Boot, Spring Data JPA, Spring REST Docs
  • MySQL, Redis, Docker, Github Actions, AWS (EC2, S3)

ERD

erd

경험

1. 후기 투표(좋아요/싫어요) 수 조회 성능 개선을 위해 커버링 인덱스 적용, 반정규화 과정을 거쳐 400만 데이터 상에서 조회 쿼리 시간 99% 개선

배경

후기 상세 조회, 후기 목록 조회 등에서 빈번히 일어나는 후기 투표(좋아요/싫어요) 수 조회 성능 개선 과정입니다. 한 회원은 한 후기에 하나의 투표만 가능합니다. 이를 위해 투표와 연관된 후기 정보, 회원 정보를 저장해야하므로 투표를 별도의 테이블로 관리하고 있었습니다.


커버링 인덱스 적용

후기의 투표 수를 확인하기 위해 투표 테이블에서 count 할 시 대용량 데이터 환경에서 조회 속도가 느려질 것으로 예상했고, 실제로 회원에 100만, 후기에 100만, 투표에 400만 데이터를 삽입하고 좋아요 수 조회 쿼리를 실행 계획을 통해 확인해본 결과 full scan 으로 약 2초가 걸렸습니다. 이를 해결하고자 커버링 인덱스를 적용하여 실제 데이터에 접근하지 않고 인덱스만으로 판단할 수 있도록 하였습니다. 기존 full scan 시보다 쿼리 응답 시간이 2초 -> 0.25 초로 약 87.5% 개선되었습니다.


반정규화

빨라졌지만 투표(좋아요/싫어요) 수 조회 API 요청 시 쿼리가 2번 날아가 약 0.5초 이상이 걸려 여전히 부담스럽습니다. 매번 count 를 하는 방식으로는 한계가 있다고 판단하고, 게시글에 좋아요 수, 싫어요 수를 의미하는 컬럼을 두는 반정규화를 시행했습니다. 0.015 초로 커버링 인덱스 적용 시보다 약 95% full scan 시보다 99% 가량 개선되었습니다.

반정규화 시 게시글의 좋아요 수, 싫어요 수 정보가 테이블 2개에 저장되게 되어 데이터 정합성에 문제가 생길 수 있습니다. 이를 방지하기 위해 투표 추가/삭제 시에 데이터 정합성을 위한 작업을 추가하였습니다. 기존보다 추가/삭제 성능이 안좋아지지만 투표 삽입/삭제보다 훨씬 빈번하게 투표 수 조회 작업이 이루어지기 때문에 이득이라고 생각합니다.


2. 조회 수의 신뢰성 확보를 위해 중복 방지, 동시성 이슈를 고려하여 조회 수 갱신 구현 및 성능 개선

배경

하루마다 상위 조회된 후기의 작성자에게 포인트 지급하는 요구사항이 있었습니다. 이를 위해 후기 별 하루 조회 데이터를 저장하고, 조회 수 신뢰성을 보장해야 했습니다. 조회 수의 신뢰성을 보장하기 위해서 한 사용자의 조회 수 어뷰징, 동시성 문제로 인한 조회 수 분실 문제를 해결해야했습니다.


후기 별 하루 조회 데이터를 저장할 방안

저장할 공간으로 RDB, 어플리케이션의 메모리, Redis 를 고려하였습니다. 후기 별 조회 데이터는 실시간으로 많은 I/O 가 일어나고 하루마다 초기화되므로 RDB 는 비효율적이라 생각했습니다. Map 과 같은 자료구조를 이용해 어플리케이션의 메모리에 저장하는 방식은 트랜잭션을 적용하기 까다롭고, 서버 종료 시 데이터 저장을 위한 별도의 로직이 필요하여 복잡도를 높인다고 판단하였습니다. 이러한 이유들과 Redis 가 제공하는 여러 자료구조의 이점, 분산 락의 활용 가능성때문에 Redis 에 후기 별 하루 조회 데이터를 저장하였습니다.


조회 수 어뷰징 방지

조회 수 어뷰징을 방지하기 위해 하루에 특정 후기를 조회한 유니크 사용자를 저장하여 구분해야했습니다. 비로그인 사용자 중 유니크 사용자를 구분하기 위한 방안으로 IP, 유니크한 쿠키 값을 고려하였으나 두 방법 모두 어뷰징에 안전하지 않습니다. 그래서 로그인 사용자만 조회 수를 증가시키도록 하고 인증 정보를 바탕으로 후기를 조회한 유니크 사용자 데이터를 Redis 에 저장했습니다.


동시성 문제로 인한 조회 수 분실 방지

조회 수 갱신 시 Redis 에 저장된 최신 조회 수에서 +1 하는 방식으로 갱신하기에 공유 자원에 대한 경쟁 상태에 빠질 수 있습니다. Lettuce 를 활용한 Spin Lock 과 Redisson 의 Pub/Sub 방식의 Lock 을 고려하였습니다. 락을 획득하지 않았을 때 락 획득 시도를 반복하지 않도록 하기위해 Redisson 의 Pub/Sub 방식을 사용하였습니다.


성능 개선

- 조회 수 갱신 시 실시간으로 DB 접근하지 않게 하여 API 응답 시간을 개선하고 DB I/O 를 줄임

사용자에게 조회 수가 실시간으로 변경되며 보여질 필요는 없다고 생각했습니다. 후기가 조회될 때마다 조회 수를 DB 에 갱신하지 않고, Redis 에 쌓아둔 후 일정 주기마다 그 시점의 최신 조회 수를 DB 에 반영하도록 구현했습니다. 이를 통해 사용자가 후기 조회 요청 시 DB 에 접근하지 않아 API 응답 시간이 개선되고, 후기 조회 수 갱신에 대한 전체적 DB I/O 도 줄였습니다.


- 조회 수 갱신 시 필요한 최신 조회 수를 탐색하는 시간복잡도를 줄이기 위해 Redis sorted set 를 활용하여 TPS 18% 개선

조회 수 갱신 시 (key, value) 가 ('후기 id + 조회 시간', '조회 수') 형태인 Map 에 저장했었습니다. 이때 조회 데이터 삽입 시 O(1), 갱신하기 위해 필요한 최신 조회 수 조회 시 O(N) 의 시간복잡도를 가졌습니다. Redis sorted set 를 사용하여 set 의 이름에 후기 id 를 포함해 후기를 구분할 수 있게 하고, 조회 시간을 기준으로 로 조회 수 value 를 정렬하도록 하였습니다. 이를 통해, 삽입 시 O(logN), 조회 시 O(1) 로 더 나은 시간복잡도를 가지게 되었습니다. JMeter 를 활용하여 변경 전후를 비교해본 결과 데이터가 200개 있는 상황에서도 TPS 가 18% 가량 증가했습니다. 대규모로 데이터가 많을수록 TPS 가 월등히 개선될 것임을 예상할 수 있습니다.


3. Github Actions, Docker Image, docker-compose 를 활용하여 배포 자동화

erd

서버 - EC2

  • 개인이 가진 컴퓨터를 활용하여 서버를 구축할 수도 있겠지만, 여분의 컴퓨터가 없기에 고려하지 않았습니다. 클라우드 서버 중에서 Naver Cloud 와 AWS 중에 고민을 하였는데, 1년간 무료로 사용할 수 있는 AWS 를 사용하였습니다.

CI 툴 - Github actions

  • Jenkins 와 Github actions 간에 고민을 했지만, Jenkins 는 추가적인 설치가 필요하고 Github actions 를 이용하면 소스 코드와 함께 Github 에서 한번에 관리할 수 있다는 점에서 관리 포인트를 줄이고자 Github actions 를 선택하였습니다. 또한 이미 사용해봤기에 추가적인 러닝 커브가 없다는 장점도 있었습니다.

배포 방식

  • 총 아래의 세 가지 방식 중에서 고민했습니다.
    1. 빌드 파일을 AWS S3 에 올리고 CodeDeploy 를 사용하는 방식
    2. 빌드 파일을 DockerHub 에 올리고 CodeDeploy 를 사용하는 방식
    3. 빌드 파일을 DockerHub 에 올리고 EC2 에서 pull 받아서 사용하는 방식
  • 결론을 먼저 말하자면, 제일 마지막인 빌드 파일을 DockerHub 에 올리고 EC2 에서 pull 받아서 사용하는 방식을 선택하였습니다. 크게 2가지 이유로, 비용 문제와 AWS 의존성을 낮추기 위함입니다.
    • 첫번째 비용 문제는 현재 AWS 프리티어를 이용하고 있는데 S3 가 월별 표준 스토리지 5GB까지, GET 요청 20,000건, PUT 요청 2,000건 무료입니다. 생각보다 넉넉한 양이 아니고, 특히 용량 5GB 가 여러 jar 파일을 보관하기에는 부족하다고 생각하였습니다. 그리고 진행하고 있는 개인 프로젝트 특성 상 이미지 처리를 많이 하고 이 또한 S3 에서 하고 있기에 여러모로 S3 를 사용하는 것은 부담스러웠습니다.
    • 두번째 AWS 의존성을 낮추고자 한 이유는 추후에 온프레미스 환경 혹은 다른 클라우드로 바꿀 수도 있기 때문입니다. 프리티어 혜택이 끝나거나 프리티어 EC2 로 아쉬운 경우에 변경 가능성이 있기에 의존성을 낮추고 싶었습니다.

4. 어플리케이션 안전성을 위해 통합/단위 테스트 작성, 테스트 커버리지 90% 달성


작성한 문서

본문 확인 (👈 Click)

About

야구장 좌석 시야 후기 공유 서비스

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published