inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

묻고 답해요

172만명의 커뮤니티!! 함께 토론해봐요.

댓글 수 구현에서 동시성 문제 해결 질문드립니다

해결됨

스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판

학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요 댓글 수 구현 강의를 해보다가 동시성 문제를 해결해보고 싶어서 비관적 락 for update를 사용하는 방법으로 한번 코드를 짜보고 테스트를 해보고 있습니다. 코드는 아래처럼 짜보았습니다 @Table(name = "article_comment_count") @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @ToString public class ArticleCommentCount { @Id private Long articleId; private Long commentCount; public static ArticleCommentCount init(Long articleId, Long commentCount) { ArticleCommentCount articleCommentCount = new ArticleCommentCount(); articleCommentCount.articleId = articleId; articleCommentCount.commentCount = commentCount; return articleCommentCount; } public void increase() { this.commentCount++; } public void decrease() { this.commentCount--; } } public interface ArticleCommentCountRepository extends JpaRepository<ArticleCommentCount, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) Optional<ArticleCommentCount> findLockedByArticleId(Long articleId); } @Service @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; private final Snowflake snowflake = new Snowflake(); private final ArticleCommentCountRepository articleCommentCountRepository; @Transactional public CommentResponse create(CommentCreateRequest request) { Comment parent = findParent(request); Comment comment = commentRepository.save( Comment.create( snowflake.nextId(), request.getContent(), parent == null ? null : parent.getCommentId(), request.getArticleId(), request.getWriterId() ) ); ArticleCommentCount articleCommentCount = articleCommentCountRepository.findLockedByArticleId(request.getArticleId()) .orElseGet(() -> { ArticleCommentCount newCount = ArticleCommentCount.init(request.getArticleId(), 0L); articleCommentCountRepository.save(newCount); return newCount; }); articleCommentCount.increase(); articleCommentCountRepository.save(articleCommentCount); return CommentResponse.from(comment); } private Comment findParent(CommentCreateRequest request) { Long parentCommentId = request.getParentCommentId(); if (parentCommentId == null) { return null; } return commentRepository.findById(parentCommentId) .filter(not(Comment::getDeleted)) .filter(Comment::isRoot) .orElseThrow(); } public CommentResponse read(Long commentId) { return CommentResponse.from(commentRepository.findById(commentId).orElseThrow()); } @Transactional public void delete(Long commentId) { commentRepository.findById(commentId) .filter(not(Comment::getDeleted)) .ifPresent(comment -> { if (hasChildren(comment)) { comment.delete(); } else { delete(comment); } }); } private boolean hasChildren(Comment comment) { return commentRepository.countBy(comment.getArticleId(), comment.getCommentId(), 2L) == 2; } private void delete(Comment comment) { commentRepository.delete(comment); articleCommentCountRepository.findLockedByArticleId(comment.getArticleId()) .ifPresent(articleCommentCount -> { articleCommentCount.decrease(); articleCommentCountRepository.save(articleCommentCount); }); if(!comment.isRoot()) { commentRepository.findById(comment.getParentCommentId()) .filter(Comment::getDeleted) .filter(not(this::hasChildren)) .ifPresent(this::delete); } } public CommentPageResponse readAll(Long articleId, Long page, Long pageSize) { return CommentPageResponse.of( commentRepository.findAll(articleId, (page - 1) * pageSize, pageSize).stream() .map(CommentResponse::from) .toList(), commentRepository.count(articleId, PageLimitCalculator.calculatePageLimit(page, pageSize, 10L)) ); } // 무한 스크롤 public List<CommentResponse> readAll(Long articleId, Long lastParentCommentId, Long lastCommentId, Long limit) { List<Comment> comments = lastParentCommentId == null || lastCommentId == null ? commentRepository.findAllInfiniteScroll(articleId, limit) : commentRepository.findAllInfiniteScroll(articleId, lastParentCommentId, lastCommentId, limit); return comments.stream() .map(CommentResponse::from) .toList(); } public Long count(Long boardId) { return articleCommentCountRepository.findById(boardId) .map(ArticleCommentCount::getCommentCount) .orElse(0L); } } @Test void concurrencyCountTest() throws InterruptedException { Long articleId = 24L; int threadCount = 10; ExecutorService executorService = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); for(int i = 0; i < threadCount; i++) { final Long writerId = (long) (1000 + i); executorService.execute(() -> { try { createComment(new CommentCreateRequest(articleId, "concurrency test", null, writerId)); } catch (Exception e) { System.err.println("Exception in thread: " + Thread.currentThread().getName() + " -> " + e.getMessage()); } finally { latch.countDown(); } }); } latch.await(); Long commentCount = restClient.get() .uri("/v1/comments/articles/{articleId}/count", articleId) .retrieve() .body(Long.class); System.out.println("최종 commentCount = " + commentCount); assertThat(commentCount).isEqualTo(threadCount); } 그런데 이렇게 했을때 맨 처음 실행을 하면 1개의 데이터만 삽입되고 나머지 9개는 소실이됩니다 그리고 한번더 실행하면 11개의 데이터가 저장되는데 맨처음 저장된 1개의 데이터 + 10개의 스레드가 저장한 10개의 데이터가 되어 11개가 됩니다. 여기서 문제가 article_comment_count 테이블에 데이터가 아예 없을때 10개의 스레드가 동시에 insert문을 날리려고해서 Duplicate entry '24' for key 'article_comment_count.PRIMARY' 이런 문제가 나오지 않나 생각이 듭니다만.. create 메서드에 @Transactional(isolation = Isolation.READ _COMMITTED)로 격리 수준을 높여봤지만 여전히 문제가 해결되지 않습니다. 혹시 제가 잘못 이해한 부분이 있을까요? 그리고 동시성 문제를 해결하려면 어떻게 해야할까요? gpt에 물어보거나 구글링해서 찾아봐도 해결이 되지 않아서 질문드립니다!

  • java
  • mysql
  • spring-boot
  • kafka
  • redis
김규혁 댓글 2 좋아요 0 조회수 234

안녕하세요 조회기능 구현에서 에러가 발생했습니다

미해결

코드로 배우는 React 19 with 스프링부트 API서버

vscode를 통해 npm start를 한 후, todo/read/숫자를 입력하니까 404에러가 발생했습니다. 그런데 vscode에서 컨트롤S를 눌렀더니 화면이 떴는데 익스플로러 상에서 새로고침을 하니까 다시 404에러가 발생하는데 이유를 모르겠습니다. 이미지를 첨부할 테니 혹시 더 필요하신 게 있으시면 답변해 주시면 감사드리겠습니다.

  • react
  • spring-boot
  • jpa
  • jwt
  • redux-toolkit
wjdwoals042 댓글 2 좋아요 0 조회수 188

Executorservice 궁금한게 많습니다.

해결됨

김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문 전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요?예 [질문 내용] 실무 웹앱의 특정 api 의 연산속도가 너무 느려 ExecutorService 를 활용하여 멀티스레드기법으로 연산속도를 올려볼 계획입니다. ExecutorService executor = Executors.newFixedThreadPool(5); 위와 같이 스레드풀의 스레드갯수를 5로 설정하려고합니다. 근데 이때 특정 api 에 동시에 5개의 요청이 들어왔다면 executor 객체가 메모리상에 5개가 생성되면서 스레드풀도 자동으로 5개 생성돼 총 25개의 스레드가 생성될것으로 보이는데 이게 서버에 부하를 주지는 않을지 궁금합니다. 아니면 실무에서는 ExecutorService 를 static 싱글톤으로 선언해서 더이상 인스턴스가 생성되지 않게 하고 캐시풀 전략을 사용해야하는건지 궁금하네요. 그리고 ExecutorService 같은건 요청이 몰리는 api 같은데서 사용하는게 아니라 배치작업같은데서 사용해야하는건지도 궁금합니다.. 궁금한게 많은 주니어 개발자입니다. 영한님의 답변을 듣고싶네요 ㅠㅠ ai 말고 ㅠㅠ

  • java
  • 객체지향
  • 동시성
  • multithread
  • thread
backendman 댓글 2 좋아요 0 조회수 173

ThreadPoolTaskExecutor에서의 가상스레드

미해결

1시간만에 끝내는 virtual thread in spring boot

안녕하세요! 너무 좋은 무료강의 감사합니다:) 강의를 듣는와중에 궁금증이 생겼는데요. log.info("3) Service virtual true: {}", Thread.currentThread().isVirtual()); 현재 스레드가 가상스레드가 맞는지 확인 하는 위와 같은 코드를 심어 실행해 보았습니다. 컨트롤러의 경우 항상 true를 리턴했고, SimpleAsyncTaskExecutor의 경우에도 true를 리턴했습니다. 그러나 예제코드에서 세팅하신 ThreadPoolTaskExecutor의 설정으로는 false를 리턴하였습니다. 해당 현상을 gpt한테 100% 공식문서 기반으로 설명하라고 하자 ThreadPoolTaskExecutors는 VirtualThread와 상호작용 중 이라는 힌트 정보를 붙인 것 이다(?) 라는 답변을 하여 이해하기가 힘들었습니다. 일단 threadPoolTaskExecutor.setThreadFactory(Thread.ofVirtual().name("vthread-", 0).factory()); 와 같은 ThreadPoolTaskExecutors의 세팅을 추가해주니 isVirtual 에대한 true값이 나오긴 했습니다. 제 부족한 견문으로는 이해가 되지 않는 현상이라 강사님이 도움을 주실수 있나해서 문의드립니다 🙂

  • java
  • spring-boot
  • 동시성
  • thread
  • virtual-thread
이북자 댓글 2 좋아요 0 조회수 289

clone() 메서드를 재정의함으로써 Deep copy로 구현한다로 이해하면 될까요?

해결됨

기초 탄탄! 독하게 시작하는 Java - Part 2 : OOP와 JVM

안녕하세요! 한가지 아리송한 부분이 있어 질문 드립니다:) 20:30 정도에 clone()이 등장하는데 clone()을 그냥 사용하는 것이 아니라 '재정의 하여 사용함으로써 Deep copy가 된다'는 의미로 이해하면 될까요?

  • java
  • 객체지향
  • jvm
  • data-structure
형씌 댓글 2 좋아요 0 조회수 156

닫힌 상태에서 문자열은 찾아지는데 열리진 않아요

미해결

[React / VanillaJS] UI 요소 직접 만들기 Part 1

AccordionItem에 작성하신 코드를 그대로 따라쳤다고 생각했습니다. 닫힌 상태에서 문자열은 찾아지는데 자동으로 아코디언이 열리진 않습니다ㅠ 추가적으로 css코드를 넣어야할까요? 알려주신 링크를 들어가서 봤는데 setOpen을 useEffect 맨 위에 넣는걸 보고 새로운 상태를 넣어야하는건가 했습니다.

  • react
  • typescript
  • dom
  • ui
  • vanilla-js
댓글 2 좋아요 0 조회수 176

캐스팅 질문 있습니다

해결됨

김영한의 실전 자바 - 기본편

기본형, 참조형 모두 대입을 할 땐 타입을 일치시켜야된다는 걸로 알고 있습니다 . 기본형 에서 대입을 할 때 타입이 다를 경우 자동으로든 수동으로든 값 자체 변환해서 타입을 일치 시켜야하고, 참조형 에서 대입을 할 때 타입이 다를 경우, 업 캐스팅(자동)이든 다운 캐스팅(수동)이든 실제 객체의 타입이 아닌 정적 타입을 일치 시켜야 되는 걸로 압니다. Child child = new Child(); Parent parent = (Parent)child; // 정적타입을 Child에서 Parent로 변환해서 정적타입을 일치시켰다. Child child2 = (Parent) parent;//정적타입을 Parent에서 Child로 변환해서 정적타입을 일치시켰다 Parent parent = (Parent)new Child();//실제 객체 타입인 Child는 정적타입이 없는 상황에서 Parent로 정적타입을 설정해서 정적타입을 일치시켰다..? 질문1. 업캐스팅 은 정적타입을 부모타입으로 설정하는것, ex) (Parent)new Child(); (Parent)child(); <- child변수는 정적타입이 Child로 가정 다운캐스팅 은 정적타입을 부모타입에서 다시 자식타입으로 변경하는 것으로 이해했는데 맞을까요? ex) (Child)parent; <-parent변수는 정적타입이 Parent로 가정 질문2. 위에 코드에서 Parent parent = (Parent)new Child(); 이 부분은 실제 객체 타입인 Child는 정적타입이 없는 상황에서 Parent로 정적타입을 설정해서 정적타입을 일치시킨건가요..? 이 부분이 전혀 이해가 되지 않습니다. 물론 업캐스팅이 생략되는 건 알고 있고 이해되지 않는 부분을 설명하고자 업캐스팅을 생략하지 않고 작성했습니다. 질문3. 강의에서 말한 부모는 자식을 담을 수 있다는 정확한 의미는 정적타입(변수의 타입)이 부모타입이고 실제 객체의 타입이 자식일 때라고 이해했는데 맞을까요? Parent parent = child; 이 경우는 Parent parent = (Parent)child; 이렇게 내부에서 타입이 일치되도록 변환을 해주기 때문에 정적타입은 결국 일치되어 부모타입의 변수에 실제 객체 타입이 자식타입으로 대입이 가능하다고 생각했습니다.

  • java
  • 객체지향
김민정 댓글 3 좋아요 0 조회수 214

9:03

해결됨

(2026 최신!) 일주일만에 합격하는 정보처리기사 실기

capitalize()는 문자열의 첫 글자를 대문자로 변환한다고 되어있는데, 예시 결과값이 왜 'Hello, world!'인가요? s는 애초에 첫 글자가 대문자니깐 s값 그대로 'Hello, World!'이 맞지 않나요?

  • python
  • java
  • c
  • 정보처리기사
꿈나무 댓글 2 좋아요 0 조회수 84

버그 어떻게 고치는지 아시나요?

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

플레이그라운드 쓸때 저 상자가 계속 유지되고 없어지지 않습니다 ㅠㅠ 방법있나요?

  • react
  • node.js
  • seo
  • graphql
  • next.js
yoonseong 댓글 2 좋아요 0 조회수 129

spring mvc project 안뜸...

미해결

자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

우선, 강의 정말 잘 듣고 있습니다. 독학하는 저에게 큰 도움이 되네요. 좋은 강의 감사합니다. 강의를 보며 실습을 진행하던 도중, 여러가지 에러들이 있었지만, 구글링을 통해 해결하였습니다. 근데 이건 도저히 해결이 안되네요... spring legacy project에서 MVC 프로젝트가 나타나지 않습니다. eclipse, java 둘다 재설치도 해보았고, spring tools도 재설치 해보았습니다. 재설치한 자바입니다. eclipse는 2025-03이고 tomcat은 v8.5 입니다.

  • java
  • spring
  • mvc
  • mvcproject
신윤창 댓글 1 좋아요 0 조회수 200

조회수를 RDB에만 저장하고 있는 서비스에서 Redis 도입 관련해서 질문입니다.

해결됨

스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판

안녕하세요. 쿠케님 강의 너무나 잘 보고 있습니다. 쿠케님 질의응답 게시글 정독하고 있는데 1:1 멘토링 부럽지 않을만큼의 고퀄리티 답변 언제나 감사드립니다. (갬동갬동) 현재 회사에서 조회수를 RDB에만 저장하고 있습니다. 이를 강의 내용처럼 'Redis 이전 & MySql 백업'을 적용 하고 싶은데 아래 흐름대로 적용 하면 될까요? 1⃣ (개발) redis 저장 및 조회, mysql 백업 코드 작성 2⃣ (개발) 기존 MySQL 데이터를 Redis에 저장하는 마이그레이션 코드 작성 => 이렇게 일회성 마이그레이션 같은 경우 테스트 코드로 한번만 돌리는데 쿠케님은 어떤 방식으로 하시나요? 3⃣ (배포 전) 1번 코드 배포 직전에 2번 로직 실행 4⃣ (배포) 1번 코드 배포 위 방식에서 발생할 수 있는 문제점 1. 3번(마이그레이션)과 4번(배포) 사이에 조회수가 누락될 가능성 - 마이그레이션 실행 후 MySQL에는 새로운 조회수가 계속 업데이트 되지만, Redis는 아직 트래픽을 받지 않음. - 즉, 마이그레이션 실행 이후 MySQL에 새로 기록된 조회수는 Redis에 반영되지 않음 → 데이터 불일치 발생 가능. 2. 처음 Redis로 전환할 때, Redis에 캐싱되지 않은 일부 조회수가 MySQL에 계속 쿼리될 가능성이 있음. - 처음 Redis로 전환할 때, Redis에 캐싱되지 않은 일부 조회수가 MySQL에 계속 쿼리될 가능성이 있음. - 특정 조회수가 빠르게 증가하면 Hot Key 이슈 발생 가능. 조회수 누락을 최소화 하는 방법 1⃣ (개발) redis 저장 및 조회, mysql 백업 코드 작성 2⃣ (개발) 기존 MySQL 데이터를 Redis에 저장하는 마이그레이션 코드 작성 및 실행 3⃣ (1번 코드 배포 전) 더블 라이트(Double Write) 모드 개발하여 운영에 적용 4⃣ (배포) 기존 MySQL 기반 조회수 코드 제거, Redis 기반으로 전환 5⃣ (배포 후) MySQL 백업 로직 실행 및 기존 MySQL 조회 로직 완전히 제거 제가 혹여나 놓친게 있거나 더 좋은 방법이 있으면 천천히 답변 부탁드립니닷! 출처: 내 뇌 + GPT 센세

  • java
  • mysql
  • spring-boot
  • kafka
  • redis
soap 댓글 2 좋아요 0 조회수 216

혹시 expo에서 adMob 적용시

해결됨

아바타 커뮤니티앱 만들기 (React Native Expo)

강의 내용은 아니고 지금 실제 앱을 만들고 있는데 거의 다 만들었는데 adMob 적용해서 막혔습니다. 아무리 해결하려고 해도 해결이 안되는데 혹시나 조언을 구할수 있을까 여쭤봅니다 expo 앱에 google admob을 추가할 때 expo-ads-admob 라이브러리를 사용하려 했지만, Expo SDK 46을 기준으로 더 이상 지원하지 않는 것 같습니다. https://docs.page/invertase/react-native-google-mobile-ads 이걸 사용해 넣으려고 했지만 계속 어떤 방법을 해도 해결이 되지 않아서 구글링도 했고 혹시 이게 해결가능한 문제인데 제가 못하는건지 아니면 이 플러그인을 사용하기가 어려운건지expo에서 궁금합니다...!

  • javascript
  • react
  • react-native
  • typescript
  • react-query
주현 댓글 2 좋아요 0 조회수 429

테블릿 기기에서의 반응형

해결됨

아바타 커뮤니티앱 만들기 (React Native Expo)

개발을 진행하면서 ios, 안드로이드 휴대폰크기로는 확인을 하면서 진행했는데 테블릿 크기에서 확인해보니 많이 작아지는 현상이 있습니다. 혹시 현업에서는 이런 처리를 어떻게 고려하시는지 궁금합니다 !

  • javascript
  • react
  • react-native
  • typescript
  • react-query
zoezoe 댓글 2 좋아요 0 조회수 142

접근제어자 2번문제

미해결

김영한의 실전 자바 - 기본편

calculateTotalPrice 를 따로 만드신 이유가 있는지 궁금해서 질문드립니다.

  • java
  • 객체지향
종근 댓글 1 좋아요 0 조회수 120

39:30

해결됨

(2026 최신!) 일주일만에 합격하는 정보처리기사 실기

n이 0일때 if(n==0)를 타고 return 값이 0에서 끝나는거 아닌가요? return값으로 0이 아닌return res가 출력이되는지 궁금합니다

  • python
  • java
  • c
  • 정보처리기사
피로한 염소 댓글 2 좋아요 0 조회수 105

24년 1회 C언어 19분 38초

해결됨

(2026 최신!) 일주일만에 합격하는 정보처리기사 실기

24년 1회 C언어 19분 38초 시저암호 문제 정답: Nd sc 1 <--이라고 적혀있는데? 변환된 문자열: Nd sc 1 <--이게 정답 아닌가요?

  • python
  • java
  • c
  • 정보처리기사
ncy84 댓글 2 좋아요 0 조회수 117

감정일기장 날짜 필터적용안됨

해결됨

한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지

(사진)

  • javascript
  • react
  • node.js
댓글 2 좋아요 0 조회수 257

map메소드에 대해 질문있습니다.

미해결

React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지

안녕하세요 강의 너무 잘 듣고 있습니다 공부를 하다가 map메소드에 대해 궁금한점이 있습니다. 예를 들어 items.map((item) => ) 여기서 어떨때에는 items.map((item) => () ) 화살표 다음에 ()가 오고 또 어떤 상황에는 items.map((item) => {}) 이렇게 {} 가 작성되는데 어떤 차이점이 있는지 궁금합니다.

  • react
  • React-Context
  • react-router
  • tailwindcss
  • react-query
부드러운 족제비 댓글 1 좋아요 0 조회수 94

연산자 우선순위 학습자료

해결됨

(2026 최신!) 일주일만에 합격하는 정보처리기사 실기

0:10 강사님~ 혹시 연산자 우선순위 표 자료를 학습자료로 올려주실 수 있으신가요? 시간날때마다 보고 어느 정도 외우려고 하는데, 영상화면이 캡처가 되지 않아서 보기가 어렵습니다 ㅠ

  • python
  • java
  • c
  • 정보처리기사
주무세요 댓글 1 좋아요 0 조회수 119

인기 태그

인프런 TOP Writers

주간 인기글