동시성 이슈가 발생할 수 있는 여러가지 상황들 동시성 이슈에 있어서는 워낙 여러가지 종류의 문제들이 있다만, 내가 지금까지 프로젝트에서 직접 경험해봤던 문제들은 다음과 같다. 재고 감소 문제 팔로우 중복 신청(더블 클릭 이슈) 교환 쌍방 제안 경험했던 이슈들에 따라서 각각 해결책도 달라졌지만, 크게 구분해보면 두 가지로 분류할 수 있을 것 같다. 이미 생성된 자원에 동시에 접근하는 경우 같은 자원을 생성하려고 동시에 시도하는 경우 구글링하면 많이 나오는 재고 감소 문제가 주로 1번에 해당한다. 여러 해결책이 많이 나오지만 주로 소개되는 건 비관락(접근하려는 row에 lock을 걸어서 다른 요청이 접근하지 못 하도록 방지)과 낙관락 (version, timestamp 등의 칼럼을 추가하여 lock을 잡지 ..
Project

사용자가 게시글을 조회하면 조회수가 증가한다 당연한 말이다. 당연한 말을 그대로 구현하기 위해 처음엔 다음과 같이 코드를 작성했다. { // (1) DB에서 card 정보 조회 Card card = cardRepository.findActiveCardById(cardId) .orElseThrow(() -> new BaseException(ErrorCode.CARD_NOT_FOUND)); // (2) JPA dirty checking을 통한 조회수 업데이트 card.increaseViewCount(); // 응답 DTO 변환(생략) } 당연하게도 사용자가 1회 조회를 할 때, 다음과 같은 두 개의 쿼리가 날아간다. card select 쿼리 card update 쿼리 조회를 위한 메서드인데, 갱신까지 해야..

여러 건을 DB에 삽입해야 할 때 나비 장터에는 이미지들을 업로드하는 상황이 있다. 교환글 하나당 최대 10장 까지 사진을 업로드할 수 있다. 이미지 등록은 다음과 같은 과정으로 이루어진다. 이미지를 bucket에 업로드한다. 생성된 bucket image 링크를 post 요청으로 서버에 보낸다. 서버는 자체 DB에 상세 이미지 링크들을 저장한다. JPA의 saveAll은 어떻게 구현되어있을까? 한 게시글이 업로드될 때, 다수의 image url도 같이 DB에 저장되어야한다. 이 프로젝트에선 JPA를 사용하고 있기 때문에 다수의 엔티티를 저장할 때 saveAll 메서드를 호출하도록 구현했었다. { // ... 생략 ... List images = cardCreateRequestDTO.images() .s..

리스트 형태의 응답은 어떻게 내려줘야되지? 응답 데이터의 갯수가 많은 상황이 있다. 대표적으로 목록을 조회해야되는 상황이 그러한 예시이다. 친구 목록, 상품 목록, 게시글 목록 등등 여러 개의 데이터를 응답으로 내려줘야 되는 상황에서 고려할 것은 당연하게도 데이터의 크기이다. 게시글 목록을 조회하려고 서버에 요청했는데, 게시글의 수가 100만개라고 가정해보자. 한 응답에 모두 실어서 보내는 것은 불가능하다고 볼 수 있다. 따라서 여러 개의 데이터를 마치 페이지처럼 특정 단위로 몇 개씩 묶어서 반환해줘야 하고, 이것을 페이지네이션 이라 한다. 페이지 방식과 스크롤 방식 web이나 app 서비스를 이용하다 보면 목록을 조회하는 방식에는 크게 2가지가 있다는 것을 알 수 있다. 페이지 방식: 목록 맨 아래에 ..

중고거래에 맛들리기 시작했다..! 말 그대로다. 대청소를 하면서 옷장 정리를 했는데, 사 놓고 안 입는 옷이라던지, 살이 쪄서 몇 번 입어보지 못 한 옷 등등.. 버리기엔 아까운 옷들을 처분하기 위해 중고거래를 시작했다. 뭔가 나에겐 필요없어진 물건들을 갖고 밖에 나가서 돈을 얻어올 수 있다는게 무슨 게임 퀘스트 하는 것 같고 해서 재미있는 것 같다ㅋㅋㅋ 좀 애매한 상황들 하지만 중고거래를 하다보면 좀 애매한 상황들이 더러 있다. 이건 버리긴 아까운데, 잘 안 팔리네.. 저건 갖고 싶긴 한데 내가 지금 돈이 없네 ㅠㅠ 모든 물건들이 다 잘 팔리는 건 아니다. 금방 팔리는 물건들이 있는가 하면 잘 안 팔리는 물건들도 꽤 있다. 이런 친구들은 버리기도 참 아까운 경우가 있어서 아쉬운 순간들이 종종 있었다. ..
문제 상황과 원인 누군가 팔로우 신청을 걸면 팔로우 신청을 받은 사용자에게 push 알림을 보내야하는 상황이 있었다. 기존의 방식은 팔로우를 거는 메인 로직과 알림을 보내는 로직이 결합되어있어 여러 문제가 있었다. @Transactional public FriendResponseDto requestFriend(FriendRequestDto friendRequestDto, String accessToken) { /* 팔로우 신청 메인 로직 */ Authentication authentication = getAuthByAccessToken(accessToken); User to_user = userRepository.findByEmail(friendRequestDto.getEmail()) .orElseThr..

솔직히 동시성 이슈 이런건 남 일이라고 생각했습니다. 동일한 자원에 짧은 시간 간격으로 들어오는 요청 같은 건 솔직히 사용자가 아주 많은 상황에서나 발생할 거라고 생각했었다. 근데, 데모 버전 발표회 당일날 발표장에서 그런 이슈를 맞이할 거라고는 예상하지 못했다.. 더블 클릭 이슈 아주 짧은 시간 간격으로 동일한 요청이 오는 상황에서는 동시성 문제가 터질 수 있다. 다만, 간과한건 이런 문제는 사용자가 많은 상황뿐만 아니라 한 사용자가 같은 요청을 짧은 시간에 여러 번 할 때도 발생할 수 있다. 더블 클릭 이슈가 이런 예시이다. 팔로우 신청 기능에서 발생한 문제..! 발표 행사장에 네트워크 환경이 좋지 않아, 사용자들이 버튼을 연타하는 상황이 빈번했다. 사용자들끼리 팔로우 신청하는 기능이 있었는데, 이 ..

Message Queue 뒷 단에서 수행되는 task에 대해서 서버에서 어떻게 확인할 수 있을까..? 음성 합성을 위한 작업은 python 컨테이너에서 수행되므로 spring boot 서버에서 컨테이너 쪽으로 작업 메시지를 넘기면 그 작업 결과에 대해 클라이언트가 알 수가 없었다. 하지만 wav 파일 생성을 마치면 해당 wav파일을 gcs 버킷에 업로드하기 때문에, 클라이언트에서 gcs로부터 다운로드 받기 위해서는 해당 작업이 끝난 시점을 알아야만 했다. 따라서, 며칠 동안 머리를 싸맨 결과 나름의 해결책을 고안했다. 끝났는지 계속 물어보기(Polling 방식) 당최 해결법이 떠오르지 않다가, 문득 하나의 생각이 머리를 스쳤다. celery backend가 있구나? celery는 작업의 결과를 backe..

음성 합성을 위한 과정 사용자의 요청에 따라 음성 파일을 합성하는 전반적인 과정은 다음과 같다. 1) 클라이언트 요청 2) 해당 음성 모델로부터 Systhesizer object initialize synthesizer = Synthesizer( tts_checkpoint = f"{tts_model_file_path}", tts_config_path = f"{tts_config_path}" ) 3) 2에서 만든 Synthesizer object로부터 tts 함수 호출(wav 파일 생성) symbol = synthesizer.tts_config.characters.characters # symbol set text = normalize_text(text, symbol) # text normalize wav..

굳이 django를 써야할까..? 한이음 멘토님과 프로젝트 관련 이야기를 하다가, 이런 이야기가 나왔다. 나: 음성 합성 관련 라이브러리가 python으로 쓰여서 백엔드 서버도 django를 사용하고 있습니다 ㅠㅠ 멘토님: 음.. 서버가 요청을 rabbitMQ 같은 message queue에 publish하면, python으로 받아서 처리하면 되니 굳이 서버까지 python으로 만들 필요는 없지요 나: (생각해보니 그렇네…!) 서버 개발 언어에 굳이 제약을 걸 필요가 없겠구나! 아무래도 이제는 django보단 spring boot가 더 익숙하기도 하고, 요즘 적극적으로 공부하고 있는 프레임워크이기 때문에 굳이 python을 사용해야하는 상황이 아니라면 django를 고집하고 있을 이유도 없겠다는 생각에 ..