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 조회수 235

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

강의자료부탁드립니다.

미해결

프로그래밍 시작하기 : 파이썬 입문 (Inflearn Original)

강의자료 아래 메일로 부탁드려요 yungki.lee@gmail.com 감사합니다.

  • python
Yung-Ki Lee 댓글 2 좋아요 0 조회수 93

조회수를 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 조회수 217

docker 에러

미해결

실리콘밸리 데이터 리더가 알려주는 Airflow 기초

명령어: docker compose -f docker-compose.yaml up 에러 메세지: ERROR! Maximum number of retries (20) reached. 사용 환경 windows 11 Home 어떤 부분 때문에 계속 오류가 발생하는걸까요?

  • python
  • sql
  • airflow
  • snowflake
[SQL 실전반 13기] Toby 댓글 2 좋아요 0 조회수 235

강의 코드를 확인할 수 있는 깃허브 URL이 있을지 궁금합니다!

해결됨

커머스 서비스로 배우는 NestJS 실전 개발 (w. Prisma, Docker, Redis, Kafka)

안녕하세요 좋은 강의 감사합니다! 혹시 강의 코드를 확인할 수 있는 깃허브 URL을 따로 제공받을 수 있는지 궁금합니다!!

  • docker
  • kafka
  • redis
  • nestjs
  • prisma
김용민 댓글 1 좋아요 2 조회수 324

39:30

해결됨

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

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

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

24년 1회 C언어 19분 38초

해결됨

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

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

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

문제 교재는 따로 없는 거 맞나요?

해결됨

코딩테스트 [ ALL IN ONE ]

- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요 코테 적용 문제들은 교재에 따로 없이 강의 아래의 리트코드 주소를 통해서 확인하고 푸는 거 맞나요?? 영어로 되어있어서 질문 올려봅니다.

  • python
  • 코딩-테스트
  • 알고리즘
나상민 댓글 2 좋아요 1 조회수 227

연산자 우선순위 학습자료

해결됨

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

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

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

파이썬 if문 질문

해결됨

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

안녕하세요. 제가 예제 볼 때 온라인 컴파일러 사이트로 직접 작성해보면서 공부를 하는 중인데요 파이썬 if문 lst 예제를 그대로 사이트에 입력하면 ========================= lst = [1, 2, 3] if lst: print("리스트는 비어있지 않습니다.") else: print("비어있지 않습니다.") ===================== else가 예제처럼 if와 같은 열에 있지 않게 되고, 에러가 뜨더라구요. 저기서 else문 지우면 "리스트는 비어있지 않습니다." 정상으로 출력 되구요. 혹시 문법은 문제 없는데 파이썬이 버전마다 다른지 궁금합니다 https://www.tutorialspoint.com/online-programming-compilers.htm 사이트는 여기 이용 중입니다!

  • python
  • java
  • c
  • 정보처리기사
박현서 댓글 3 좋아요 0 조회수 183

감성 분석을 하려면 어떤 부분을 공부해야 하나요?

미해결

모두의 한국어 텍스트 분석과 자연어처리 with 파이썬

프로젝트에서 감성 분석과 주제 찾는 작업이 필요합니다. 주제 찾기는 강의를 통해 배울수 있을거 같은데요 감성 분석은 어떤것을 공부하면 좋을까요?

  • python
  • 머신러닝
  • 빅데이터
  • NLP
  • 텍스트마이닝
  • data-clustering
  • 데이터-리터러시
예비군갈나이 댓글 2 좋아요 0 조회수 232

강의자료 부탁드립니다

미해결

프로그래밍 시작하기 : 파이썬 입문 (Inflearn Original)

seizeejday@naver.com 감사합니다 : )

  • python
Zoey 댓글 2 좋아요 0 조회수 98

5:38

해결됨

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

int, Integer를 같다고 판단한다 했는데, 범위를 넘어선 정수도 동일한가요? 예를 들어서, int num1 = 128; Integer num2 = 128; 이라면 num1 == num2 : true 인가요?

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

ORM 연관관계

해결됨

실전! FastAPI 입문

안녕하세요! Nest에서 Fast API로 컨버젼중입니다. Nest는 Many to Many, Many to One 등등 이러한 연관관계를 설정해서 테이블을 생성합니다. Fast API에서는 아래 코드처럼 단순히 join만 걸어두고 사용하면 되는 걸까요?? class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, index=True) username = Column(String(256), nullable=False) password = Column(String(256), nullable=False) todos = relationship("ToDo", lazy="joined")

  • python
  • 리팩토링
  • orm
  • FastAPI
  • pytest
Full Stack 개발자 댓글 2 좋아요 0 조회수 148

id관련

해결됨

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

안녕하세요. 강의 잘 듣고 있습니다. 제가 원래 질문이 많은데 개념을 이해가 잘 되도록 쉽게 설명해 주셔서 질문 드릴게 별로 없네요. 보통 api path에 id를 추가하시는거 같은데요. 현업에서도 auto_increment나, snowflake id같은 db에서 쓰는 id를 그대로 넣고 사용하시나요? 아니면 prefix등을 추가하여 조금 더 가공을 한다든지 하시나요? 숫자만 들어가니 좀 밋밋해 보이기도 하고 알아보기도 힘들거 같기도 해서요.

  • java
  • mysql
  • spring-boot
  • kafka
  • redis
굿데이 댓글 2 좋아요 0 조회수 150

2023년 2회 정렬코드문제 답이 2개 아닌가요?

해결됨

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

2023년 2회 29분 정렬 구현 문제 답이 2개 아닌가요? 저는 >, < 모두 정답인 것 같아서요 ㅠㅜ

  • python
  • java
  • c
  • 정보처리기사
김준혁 댓글 2 좋아요 0 조회수 93

섹션3과 섹션4 같은 영상으로 확인됩니다

해결됨

커머스 서비스로 배우는 NestJS 실전 개발 (w. Prisma, Docker, Redis, Kafka)

확인부탁드립니다

  • docker
  • kafka
  • redis
  • nestjs
  • prisma
데르데르데 댓글 1 좋아요 0 조회수 173

미국 주식 분봉 데이터를 얻기 위한 방법은 없나요?

해결됨

파이썬 주식 매매 봇으로 주식시장 자동사냥하기

한국투자증권의 api에서는 해외주식의 경우 1달까지만 분봉 데이터를 제공한다고 나옵니다. 미국 주식 분봉 데이터를 얻기 위한 방법은 없을까요?

  • python
  • pandas
  • 차트분석
  • 퀀트
  • trading
Sett 댓글 2 좋아요 0 조회수 1109

인기 태그

인프런 TOP Writers

주간 인기글