본문 바로가기

make-delivery 프로젝트11

[#9] 같은 주문에 2명의 라이더가 동시에 배달하는 문제 해결 - Redis Transaction을 이용하여 데이터 atomic 보장 제가 진행하는 make-delivery 배달 앱 서버 프로젝트는 라이더들이 배차가 되지 않은 주문 목록 중에서 배차 요청을 하여 배달할 주문을 선택하는 로직이 있습니다. 하지만 만약 동시에 라이더들이 같은 주문에 배차 요청을 한다면 동시에 두 라이더가 배달을 하게 되는 것으로 나오며 주문 테이블에 라이더 정보는 더 늦게 배차 요청을 한 라이더가 입력되는 문제가 발생할 것이라 판단했습니다. 이는 데이터의 atomic이 보장되지 않는 문제이며 Race Condition과 비슷한 문제라고 판단했습니다. 기본적으로 이 문제가 발생한 원인을 설명하기 위해 간단한 예제를 설명하겠습니다. public int getNext() { return value++; } 얼핏 보기에 value++; 의 연산은 단 한번으로 이루.. 2020. 12. 27.
[#10] Ngrinder를 이용해 성능테스트 : WAS, DB, Nginx등 서버에 병목이 있는지 확인하는 과정 Ngrinder 사용이유 제가 진행했던 make-delivery라는 프로젝트는 배달앱 서버를 구축해보는 것을 목표로 진행했습니다. 제가 만든 프로젝트 서버가 성능이 어느정도 되는지 , 동시에 몇명의 사용자가 요청해도 서버가 견뎌 낼 수 있는지가 궁금했고 객관적인 지표를 얻는 테스트를 해보고 싶었습니다. 최종 테스트 환경 - 위 스크린샷은 최종 서버 구성에서 총 3000명의 동시 사용자 성능테스트 결과입니다. - 네이버 클라우드 플랫폼에서 8vCPU RAM 8GB 서버를 한대 띄워 Ngrinder를 설치하였습니다. - 배달음식점 조회 기능을 성능테스트했고 이 조회 기능은 음식 카테고리 별로 단순한 조회 쿼리 기능입니다. - Row는 100개 정도로 테스트했고 Redis 캐시를 이용해 음식점 조회 기능을 빠.. 2020. 12. 21.
[#11] 성능 테스트 결과에 따라 비용을 고려하여 적절한 서버 구조 설계 과정 제가 진행했던 make-delivery라는 프로젝트는 배달앱 서버를 구축해보는 것을 목표로 진행했습니다. 사용한 서버 목록 - WAS 서버 (자바+스프링) 3대 - Mysql 서버 2대 (Master, Slave) - Redis 서버 1대 (세션,캐시) - Nginx 웹서버 1대 - Jenkins 서버 1대 - Vault 서버 1대 - Ngrinder 서버 1대 - Pinpoint 서버 1대 프로젝트 서버 구조에 대한 전반적인 설명 - 사용자는 Nginix IP로 Http 요청을 보냅니다. - Nginix의 Reversed Proxy 기능을 통해 WAS서버 3대에 부하를 분산시킵니다. - WAS서버는 데이터 접근을 위해 Mysql서버와 Redis서버에 요청을 보냅니다. - Jenkins서버는 CI/CD .. 2020. 12. 17.
[#7] TransactionManager가 DataSource정하는 로직을 늦추기 - LazyConnectionDataSourceProxy tjdrnr05571.tistory.com/14?category=876333 Mysql Replication Spring에서 Master/Slave 이중화 with Docker 이글에선 단일서버에서 Mysql Replication을 port를 나누어 하는 방법을 다룹니다. 목차 - 내 프로젝트에서 Mysql Replication을 사용해야 하는 이유 - Mysql Replication의 동작 원리 - Docker로 Mysql 컨테이너.. tjdrnr05571.tistory.com 위 링크에서처럼 Mysql Replication을 Spring에서 구현하던 중 @Transactional로 트랜잭션을 시작 시켜줄 때 DataSource가 무조건 기본 Master DataSource로만 쿼리가 가는 문제가 생겼.. 2020. 12. 5.
[#8] Mysql Replication - Spring에서 Master/Slave 이중화 with Docker 이글에선 단일서버에서 Mysql Replication을 port를 나누어 하는 방법을 다룹니다. 목차 - 내 프로젝트에서 Mysql Replication을 사용해야 하는 이유 - Mysql Replication의 동작 원리 - Docker로 Mysql 컨테이너 두개 띄우기 - Spring 에서 AOP를 이용하여 쿼리 분산 구현 구현순서 1. Docker를 이용하여 Mysql 컨테이너를 두개 띄우고 각각 Master와 Slave를 할당합니다. 2. Spring applcation level에서 DataSource를 나누어 읽기는 Slave로 쿼리를 쓰기/삭제는 Master로 쿼리를 보냅니다. Mysql Replication을 사용해야 하는 이유 1. 부하분산 (일종의 Scale out) 제가 개발하는 ma.. 2020. 12. 3.
[#6] Jenkins를 이용하여 CI/CD 환경 구축해보기 제가 진행했던 make-delivery 프로젝트를 배포하기 위해서는 리눅스 서버에서 Git Pull을 하여 소스코드를 가져온 다음 maven을 이용하여 build를 하고 jar파일을 실행해야 했습니다. 하지만 매번 git pull을 하고 빌드를 하고 jar파일을 실행하는 것은 지루하고 반복적인 과정이며 불필요한 시간 낭비였습니다. 따라서 이러한 문제를 해결하기 위해 여러 글들을 읽어봤고 예전에 Agile방식에 대한 글을 읽었던 경험이 생각나 검색 후 CI (지속적 통합)이라는 개념을 알게 되었습니다. CI란 개발자들이 빠른 주기로 작업한 내용을 통합 브랜치에 통합하고 빌드하는 개발방식을 말합니다. # CI를 해야하는 이유 tjdrnr05571.tistory.com/12 IT 기업들에서 왜 Jenkins를.. 2020. 11. 26.
[#5] 성능을 위해 Redis keys 대신 scan 이용하기 Redis scan을 사용하는 이유 레디스의 명령어중 keys를 이용하면 모든 키값들을 가져올 수 있지만 이키값들을 가져오는동안 다른 명령어를 수행하지 못합니다. 따라서 성능에 영향을 줄 수 있어 레디스에서는 scan,hscan을 권장합니다. redis의 keys 명령어는 시간 복잡도가 O(N)입니다. 레디스는 싱글스레드로 동작하기 때문에 이처럼 어떤 명령어를 O(n)시간 동안 수행하면서 lock이 걸린다면 그시간동안 keys 명령어를 수행하기 위해 멈춰버리기 때문입니다. Redis에서 제공하는 Scan명령어는 Keys처럼 한번에 모든 레디스 키를 읽어오는 것이아니라 count 값을 정하여 그 count값만큼 여러번 레디스의 모든 키를 읽어오는 것입니다. (기본 count값은 10입니다) 예를들어 레디스.. 2020. 10. 25.
[#4] Spring에서 중복되는 로그인 체크 기능을 AOP로 분리하기 마이 페이지에서 회원의 정보를 확인하거나 회원의 접근권한이 필요한 메소드에서 로그인을 했는지 확인해야하는 로직이 중복되는 문제가 발생하였습니다. AOP 를 적용하기 전에는 이러한 로그인 했는지 확인하는 부가기능이 회원의 권한이 필요한 모든 메소드에서 반복되었습니다. (세션에 로그인 정보가 남아있지 않다면 예외를 던져주고 있다면 진행하는 로직) 예를들어 배달 앱에서 내 프로필 수정하려고 하거나 고객이 가게에서 주문을 요청하는 메소드에서 주문을 하려면 고객으로 로그인이 되어있어야 하기 때문에 로그인 체크 로직이 필요합니다. 이러한 불필요한 반복은 줄여야하고 비즈니스 로직과는 전혀 관련이 없기 때문에 분리해낼 수 있다면 가장 좋은 방법이 될 것입니다. 토비의 스프링을 책을 읽었던 경험으로 AOP를 적용하면 분.. 2020. 10. 16.
[#3] 정확히 트랜잭션이 롤백 되었을 때 장바구니를 복원하기 -TransactionSynchronization afterCompletion (Rollback hook) @Transactional을 적용하여 어떠한 메소드를 실행한다면 롤백이 되었을 때 DB에 관련된 추가, 삭제등 로직은 @Transactional에 적용되어있는 AOP 로직에 의해 저절로 롤백이 됩니다. 하지만 레디스나 다른 스토리지에 어떠한 데이터를 추가하거나 삭제할 시 이를 수동으로 롤백시켜줘야 합니다. try-catch문을 이용하여 롤백되었을 때 로직을 처리해줄 수 있기는 합니다. 보통은 @Transactional 메소드 안에서 try catch문을 이용하여 RuntimeException이나 Error가 생겼을 시 롤백이 되면 catch문에서 잡는 방식으로 롤백이 되었을 때 로직을 수행하는데 이는 문제점이 있습니다. 롤백이 되었을 때 RuntimeException이나 Error가 생겨서 catch문 .. 2020. 10. 15.
[#2] Redis에 한번에 많은 데이터 추가 시 네트워크 병목 개선하기 - Redis Pipeline 이용하기 문제 상황 예를들어 레디스에 리스트를 저장하였고 그 리스트에 한번에 여러개의 원소를 추가하는 상황이라고 가정해보겠습니다. 레디스에 기본적으로 한번의 추가 연산을 하면 O(1)시간이 들며 요청을 보내고 다음과 같이 응답 값을 받습니다. 레디스는 싱글스레드와 이벤트루프 기반으로 비동기방식으로 요청을 처리하기 때문에 고성능입니다. 하지만 기본적으로 TCP 기반의 네트워크 모델을 따르기 때문에 네트워크 I/O 에서 병목이 생길 수 있는 가능성이 있습니다. 하지만 매번 요청을 할 때마다 응답값을 받기 때문에(요청을 보내고 응답을 받을때까지는 blocking이 됩니다) 한번에 수십만개의 요청을 보낸다면 이 응답값을 매번 받는 것도 부하가 생길 수 있습니다. 즉, 레디스 서버에 반복문을 돌며 여러번 리스트의 원소를 .. 2020. 10. 8.