2024년 7월 29일 부터 8월 30일까지 한달 간 동시성 테스트를 경험해보기 위해 식당 예약 및 원격으로 줄을 설 수 있는 서비스를 만들었다. 서비스를 만들기 전에 만든 아키텍처 설계와 ERD 설계는 아래 사진과 같다.
위의 사진처럼 route53에 가비아에서 구매한 도메인을 EC2서버에 연결해 localhost:8080에서 접속할 수 있었던 사이트를 catchline.site
에서 접속할 수 있게 했고, 배포된 사이트에서 데이터베이스를 사용할 수 있기 위해서 Aamazon RDS
에서 MySQL
을 사용했다. 그 후 클라이언트에서 요청을 보내면 AWS Certificate Manager와 Aamazon ELB를 거쳐서 EC2서버에 요청이 가도록 설계를 했다.
백엔드 개발자로써 이번처럼 본격적으로 프로젝트를 하는 것이 처음이라 개발을 하기 전에 전체적인 협업규칙을 정하기로 했다. 그 결과 우리팀이 정한 협업 규칙은 여러가지가 있지만 대표적으로 특정 기술을 선택하기 전에 왜 이 기술을 사용해야 하는지 팀원들과 함께 충분한 대화를 통해서 인지를 하고 기술을 사용하는 것이었다. 프로젝트 개발이 끝난 지금도 이런 규칙을 정한 것이 되게 괜찮다고 생각이 들었다. 그 이유는 포트폴리오를 작성할 때나 면접을 볼 때 왜 Jwt를 사용하셨나요? 와 같은 질문들이 되게 많이 나온다고 알고 있고, 이러한 질문들에 대비를 할 수 있기 때문이다.
한달 동안 프로젝트에서 구현한 기능들은 대략적으로 설명해보자면 CRUD는 기본적으로 식당,리뷰,스크랩,웨이팅,예약,유저 추가 삭제 편집 등에서 구현했고, 스프링 시큐리티, jwt, 세션 사용과 querydsl를 사용해 페이지네이션, 검색, 정렬 기능 구현, SSE를 사용해 알림 기능등을 구현했다. 또한 동시성 테스트를 하기 위해서 syncronized
를 사용해 예약등록 메서드를 동시성 테스트를 해봤다.
위에서 내가 한달 동안 팀원들과 함께 만든 서비스의 소개, 협업 규칙, 설계, 구현한 기능을 소개했으니 한달동안 발생한 주요한 이슈들 몇가지를 소개해보겠다.
동시성 테스트를 하면서 발생한 이슈부터 말해보겠다.
첫 번쨰로 20명의 사용자가 동시에 하나의 식당에 예약을 할 때 1명의 사용자를 제외하고 19명의 사용자가 예약등록을 실패해야하는데 처음에 내가 동시성 테스트를 실행했을때에는 7명의 사용자가 예약 등록을 성공하는 이슈가 발생했다. 그래서 나는 이 이슈의 원인이 내가 ExecutorService executorService = Executors.newFixedThreadPool(20)
을 사용해서 스레드 풀에 총 20개의 스레드를 만들고 테스트를 실행했는데 하나의 스레드가 예약등록 메서드를 실행 중이고 아직 메서드가 종료되지 않았을 때 다른 스레드가 예약등록 메서드를 실행해서 발생한 이슈라고 생각해 예약등록 메서드에 트랜잭션과 syncronized를 추가해 이 이슈를 해결했다. 그러나 이 이슈를 해결한 후 바로 다른 이슈가 발생했다.
두 번쨰로 트랜잭션과 syncronized를 추가한 후에는 1명의 사용자를 제외하고 나머지 모든 사용자가 예약등록에 실패하는 결과가 올바르게 나왔는데 테스트를 10번 실행하면 3번 정도는 테스트가 실패하는 이슈가 발생했다. 결론적으로 이 이슈의 원인은 트랜잭션과 syncronized를 함께 사용해서 발생한 결과인데 그 이유는 트랜잭션을 사용하면 메서드의 앞뒤로 transaction begin, transaction commit 과 같은 코드들이 생기는데 이 코드들은 syncronized의 동시성 제어의 영역이 아니기 떄문에 transaction commit이 끝나기 전에 다른 스레드가 메서드를 실행시키는 경우가 발생해서 위와 같은 이슈가 발생했었다. 그래서 이 이슈는 예약 등록 메서드에서 트랜잭션을 제거하고 해결했다.
세 번쨰로 200명이 동시에 예약을 했을 때 예약 등록에 성공하는 유저가 memberId 1부터 20번 정도로만 테스트를 실행시켰을 때 나오지 않았다. 이 이슈의 원인은 디버깅을 통해서 해결했는데 내가 바라는대로 200개의 스레드가 동시에 예약 요청을 하지 않고, 20개 정도의 스레드가 먼저 예약을 시도한 후에 나머지 스레드가 예약을 동시에 실행하고 있어서 발생한 이슈였다. 그래서 나는 이 이슈를 CountDownLatch
의 startLatch.await()
을 사용해서 모든 스레드가 준비 상태에 도달할 때까지 대기하고 모든 스레드가 준비 완료되면 startLatch.countDown()
을 호출해서 모든 스레드가 동일한 조건에서 동시에 메소드를 실행하게 해서 이슈를 해결했다. 아래의 이미지가 내가 동시성을 테스트한 코드이다.
다음은 배포과정 중에서 발생한 이슈인데 처음에 EC2와 RDS를 사용해서 배포를 한 후에 확인을 했을 때 카카오맵 API가 정상적으로 연동이 되지 않고, HTTP
로 배포가 되어서 보안이 매우 취약한 이슈였다. 그래서 나는 이 이슈들을 AWS의 Route53과 Certificate Manager를 사용해서 SSL 인증을 받아 HTTPS
로 배포를 하면서 이 이슈를 해결했다.
나중에 알고보니 카카오맵 API 연동 문제는 HTTP를 사용해서 발생한 것이 아니라 다른 해결방법이 있었던 것 같아서 괜히 HTTPS로 배포했나 싶기도 했지만 보안 문제를 생각하면 SSL인증을 받아 HTTPS로 배포한 것이 괜찮은 선택이었던 것 같다.
내가 겪은 주요한 이슈들은 이정도였지만 팀원들이 겪었던 이슈들도 매우 많았던 것 같다.
이번 프로젝트를 하면서 제일 많이 얻은 것은 스프링에 대한 기본적인 숙련도인 것 같다. 이번 프로젝트에 기본적인 CRUD를 사용해서 만든 기능들이 많아 한달 전보다 훨씬 스프링에 익숙해졌고, 또한 수많은 멘토링을 통해서 클린코드에 좀 더 가까워지게 코드를 작성할 수 있게 되었다.
그러나 아직 모르는 스프링에 대한 기능들이 많기 때문에 좀 더 많이 경험을 해보고 익숙해지고 싶다. 그리고 이번에 동시성 테스트에서 Redis
를 사용해보는 경험을 해보고 싶었는데 시간이 부족해 사용해보지 못해 약간 반쪽짜리 동시성 테스트 였다는 생각이 들었다. 그리고 만들고 보니 기능을 많이 구현했다고 생각했지만 핵심적인 기능들이 조금 적었던 것 같아 여러모로 부족했던 점이 많은 프로젝트였던 것 같다. 하지만 그만큼 얻은 것도 많고 다음 프로젝트는 무려 3달이 넘는 기간동안 만들기 때문에 훨씬 디테일을 많이 챙기면서 만들어 볼 수 있을 것 같다.