코드를 한줄 한줄 같이 치면서 학습을 하는데요, 강사님께서도 코드 한줄 한줄 같이 치신다고 하셨으나 이번 , 다음 강의에서는 yml, controller, service, vo등 모든 것이 작성이 되어있더라고요. 따로 설명 하는 부분 없이 바로 서버 실행 하시는 거 보고 당황했습니다. 코드를 직접 쳐가면서 학습을 하고 싶은데 그러지 못한거 같아 아쉽고, 어디 까지 깃허브에서 코드를 가져와서 사용해야 하나 궁금합니다.
127, 128 섹션 관련 문의드립니다. 2개의 오더 마이크로서비스 각각에 연결된 데이터베이스로 인한 동기화 문제를 위해 카프카 커넥터를 활용하여 하나의 단일 디비로 문제를 처리한다고 하셨는데, 결국 2개의 오더 마이크로서비스에 카프카 커넥터를 사용하지 않고 동일한 디비 1개를 직접 연결해서 사용하면 동기화 문제가 발생하지 않는건 마찬가지아닌가요? 동일한 오더 마이크로서비스를 스케일아웃 하는 상황에서 카프카 커넥터를 사용하는게 목적에 맞는지 의아해서 질문드립니다.
안녕하세요 쿠케님 강의 잘 보고 있습니다! 강의를 보다가 갑자기 궁금한 점이 생겨서 질문 드립니다. 샤딩의 기준이 현재는 article_id로 되어 있는데, 특정 샤드에 댓글 데이터가 엄청 생성되어서 불균형하게 저장이 되는 경우도 있을까요?? 있다면 샤딩의 기준을 다시 정의하는 일도 있는지 궁금합니다. 항상 잘 보고 있습니다. 감사합니다.
안녕하세요! 강의 잘 듣고 있습니다. 좋은 강의 감사합니다. 실습하다가 오류가 생겨 문의 드립니다. void like(Long articleId, Long userId, String lockType) { restClient.post() .uri("/v1/article-likes/articles/{articleId}/users/{userId}/" + lockType, articleId, userId) .retrieve(); } @Test void likePerformanceTest() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); // 100개의 스레드 풀 생성 // 각 lock type별로 테스트 likePerformanceTest(executorService, 1111L, "pessimistic-lock-1"); likePerformanceTest(executorService, 2222L, "pessimistic-lock-2"); likePerformanceTest(executorService, 3333L, "optimistic-lock"); } void likePerformanceTest(ExecutorService executorService, Long articleId, String lockType) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3000); System.out.println(lockType = " start"); like(articleId, 1L, lockType); long start = System.nanoTime(); for (int i = 0; i < 3000; i++) { long userId = i + 2; // String finalLockType = lockType; executorService.submit(() -> { like(articleId, userId, lockType); latch.countDown(); }); } latch.await(); long end = System.nanoTime(); System.out.println("lockType = " + lockType + ", time = " + (end - start) / 1_000_000 + " ms"); System.out.println(lockType + " end"); Long count = restClient.get() .uri("/v1/article-likes/articles/{articleId}/count", articleId) .retrieve() .body(Long.class); System.out.println("count = " + count); } 여기서 '람다 식에 사용되는 변수는 final 또는 유사 final이어야 합니다' 라는 오류가 뜨더라고요. // String finalLockType = lockType; 부분 주석 해제하고 람다 내부에 like(articleId, userId, finalLockType); 으로 하면 start lockType = start, time = 914 ms start end count = 0 start lockType = start, time = 589 ms start end count = 0 start lockType = start, time = 567 ms start end count = 0 으로 출력도 잘 안 나옵니다. 애플리케이션 콘솔에는 아래 로고만 찍히고 나머지는 안 나옵니다. Hibernate: select alc1_0.article_id,alc1_ 0.like _count,alc1_0.version from article_like_count alc1_0 where alc1_0.article_id=? Hibernate: select alc1_0.article_id,alc1_ 0.like _count,alc1_0.version from article_like_count alc1_0 where alc1_0.article_id=? Hibernate: select alc1_0.article_id,alc1_ 0.like _count,alc1_0.version from article_like_count alc1_0 where alc1_0.article_id=? 어느 부분이 문제일까요? ArticleLikeController에서 count 경로는 테스트처럼 뒤에 /count 추가했습니다.
운영중인 서비스에서 선착순 100명 이벤트를 적용한다고 가정하겠습니다. redis를 통해 100명을 제한했고, kafka를 적용하여 부하를 줄여주는 것은 까지는 이해했습니다. 부하를 줄이는 방법이 kafka를 적용할때 때 provider가 topic을 생성하고 consumer가 topic을 가져와서 DB에 입력하는 작업을 하는 것으로 이해했는데요. 만약 이게 실제 운영 환경이라고 가정했을때 궁금한것은 다음과 같습니다. 사용자가 이벤트 신청 redis에서 쿠폰 생성 수량 확인 결과 생성 가능한 조건 임으로 새로운 쿠폰 발급 provider가 새로운 토픽을 생성 토픽을 생성한 그 순간 바로 직후, 사용자는 새로운 쿠폰이 발급된 것으로 확인 해야함. 그치만 consumer에서 topic을 가져오기 전으로 DB에는 새로운 쿠폰이 생성되지 않음. 쿠폰을 사용(또는 확인) 하려고 DB에서 select해보니 쿠폰이 없음 consumer가 이제서야 쿠폰 생성 이 경우에서 보는 것과 같이. provider가 topic을 생성하고 consumer가 topic을 가져와서 DB에 넣는 과정 사이에 사용자가 select를 진행하는 케이스가 있을것같습니다. 이 부분은 어떻게 해결할 수 있을까요? 혹시 다음과 같이 해결 할 수 있을까요? provider가 topic을 생성하는 과정에서 발급 내역을 redis에 입력 consumer가 모든 토픽을 전부 사용하여 DB에 입력하기 전까지 redis에 입력되어 있는 쿠폰 정보로 사용자에게 보여줌 consumer가 모든 토픽을 사용했을때(= 생성된 모든 쿠폰정보를 DB에 입력했을때) redis에 있는 쿠폰정보는 삭제하고 DB에서 select해서 보여줌. 궁금합니다.
안녕하세요 빅디님 ! gcc 설치 중에 오류가 나서 yum repository 삭제 후 다시 시도해 보았는데, 계속 오류가 나서 질문 드립니다. ㅠㅠ 어떤게 문제일까요..? yum repo 삭제는 다음과 같이 진행 하였습니다. [root@server02 ~]# cd /etc/yum.repos.d/ [root@server02 yum.repos.d]# rm -rf remi.* remi-* [root@server02 yum.repos.d]# [root@server02 yum.repos.d]# cd /var/cache/yum/ [root@server02 yum]# rm -rf x86_64 [root@server02 yum]# [root@server02 yum]# yum clean headers Loaded plugins: fastestmirror, refresh-packagekit, security Cleaning repos: base cloudera-manager extras updates 0 header files removed [root@server02 yum]# yum clean packages Loaded plugins: fastestmirror, refresh-packagekit, security Cleaning repos: base cloudera-manager extras updates 0 package files removed [root@server02 yum]# yum clean metadata Loaded plugins: fastestmirror, refresh-packagekit, security Cleaning repos: base cloudera-manager extras updates 0 metadata files removed 0 sqlite files removed 0 metadata files removed yum install -y gcc* 명령어 입력시 발생하는 오류 입니다. [root@server02 ~]# yum install -y gcc* Loaded plugins: fastestmirror, refresh-packagekit, security Setting up Install Process Loading mirror speeds from cached hostfile Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os&infra=stock error was 14: PYCURL ERROR 6 - "Couldn't resolve host 'mirrorlist.centos.org'" Error: Cannot find a valid baseurl for repo: base 추가로, CentOS-Base.repo 파일 내용 첨부드립니다. [root@server02 yum.repos.d]# cat CentOS-Base.repo # CentOS-Base.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [base] name=CentOS-$releasever - Base mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #released updates [updates] name=CentOS-$releasever - Updates mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/updates/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #additional packages that may be useful [extras] name=CentOS-$releasever - Extras mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #additional packages that extend functionality of existing packages [centosplus] name=CentOS-$releasever - Plus mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/centosplus/$basearch/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #contrib - packages by Centos Users [contrib] name=CentOS-$releasever - Contrib mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=contrib&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/contrib/$basearch/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 감사합니다.
재시도조차 실패한 메시지들은 dlt 로 이동하게 되고 이 메시지들에 대한 처리를 위해 @KafkaListner 를 사용해서 처리하는 방법을 보여주셨는데요. 리서치를 하다보니 @DltHandler 기능이 있는걸 알게됐습니다. dlt 를 처리한다는 부분에서 @DltHandler 가 좀 더 어울릴거 같은 느낌인데 @KafkaListener 로 처리하신 특별한 이유가 있으실까요?
kafka @RetryableTopic 에 대해 알아보다보니 궁금한점이 생겨 질문 드립니다. @RetryableTopic 가 없어도 retry 는 기본적으로 진행하는거 같은데요. 제가 알아본 바로는 아래와 같은 차이점이 있는것 같았습니다. @RetryableTopic 을 사용하지 않으면 'dlt' 로 메시지가 이동되지 않는다. @RetryableTopic 을 사용하면 'dlt' 토픽이 없는 경우 자동으로 만들어주고 dlt 토픽으로 메시지를 이동시켜준다. @RetryableTopic 을 사용하지 않으면 재시도는 하지만 재시도동안에는 partition 을 blocking 한다 (= 블로킹). @RetryableTopic 을 사용하면 재시도 하기전에 retry 토픽으로 이동시키고 consumer 의 스레드를 blocking 하지 않고 별도 스레드에서 retry 를 진행한다. (= 논블로킹) 강좌에서는 retry 중에는 partition 이 blocking 된다고 하셨는데, 그 부분과 좀 다른거 같아서 문의 드립니다..! 만약 @RetryableTopic 이 논블로킹으로 별도 스레드에서 진행이 된다면 순서보장이 안되는거라서 순서보장이 필요하다면 이걸 사용하면 안되는게 아닌가 싶습니다.
예제에서는 StringSerializer 와 StringDeserializer 를 사용하도록 설정하고 ObjectMapper 를 통해 직렬화/역직렬화를 해주셨는데요. 혹시 JsonSerializer 와 JsonDeserializer 를 사용하지 않는 이유가 있을까요? 그리고, JsonDeserializer 를 사용하든 StringDeserializer 를 사용하든 역직렬화를 할 때 실패하게 되면 offset commit 이 되지 않고 재시도를 하는동안 해당 message 의 partition 은 blocking 된다고 이해하고 있는데 맞을까요? 이 경우에도 retry 이후, 해결 안되면 dlt topic 으로 이동하는게 맞을까요?
리서치를 하다보니 실무에서는 auto.create.topics.enable=false 설정을 사용하는게 좋다는 글을 많이 보게 되었습니다. 네이밍 컨벤션이나 예상치 못한 topic 의 생성 등을 방지하기 위함으로 이해했는데요. 그럼, xxx.dlt 와 같은 topic 들도 직접 생성을 해줘야 하는지 궁금합니다. 그리고, 실제로 실무에서 해당 설정을 많이 사용하는지 또한 궁금합니다 🙂
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요 선생님! 지난번에 남겨주신 답변내용을 보면서 전략도입의 배경부터 처리과정까지 복기해보았습니다. 그러다가 의문이 생긴 점이 있습니다. 1) 의문점 Request Collapsing, 캐싱중복적재를 제외하고 캐싱하여 데이터를 추출하는 과정만을 보았을때, 원본과 캐싱 데이터 모두 Redis에서 추출해오는 것인데, 원본추출과 비교하였을때 과정적으로 어떠한 차이점이 추가적으로 있있기에 별도의 과정으로 간주하는 것일까? 즉, "캐싱하여 가지고 오는 과정"과 "원본 데이터 추출"을 따로 보고 계셨기에, 어떠한 차이점이 있는지 의문점이 들었습니다. 2) 의문점이 생긴 이유 단순하게 간략히 말씀드리자면, 조회수 원본데이터는 Redis에서 가져오는 조회수이고, 캐싱해서 가져오는 것 역시 Redis에서 가져오는 조회수로 보여집니다. 원본데이터가 MySQL과 같은 디스크 조회 비용이 큰 저장소에 들어있는 것이 아니라, 동일한 In Memory database인 Redis에서 가져오는 것이기에 성능/비용적으로 캐싱 데이터를 가지고 오는 것에 큰 이점을 느끼지 못하였습니다. 세부적으로 살펴보았을때, ViewClient에서 원본 데이터를 가지고 오는 경우 아래 로직을 통해 Redis에서 추출합니다. articleViewCountRepository.read(articleId); 이떄 key는 view::article::#articleId::view_count 입니다. ViewClient에서 Aspect를 처리하여 캐싱 데이터를 가지고 오는 경우 Redis에서 추출합니다. 이때 key는 articleViewCount::#articleId입니다. 이 과정에 대해 캐싱을 하는 목적을 생각해보았을때(=원본데이터 추출에 시간이 오래 걸릴 경우 성능이 빠른 다른 데이터저장소를 운용하여 이곳에서 데이터를 추출해오기 위함), 동일하게 Redis에서 추출하는 데이터임에에도 key를 별도로 운용하고, 데이터를 추출하는 과정도 다르게 가져가는(간주하는) 이유가 무엇인지 궁금하여 문의코자 합니다(물론 전체 로직을 살펴보았을때는 기존 대비 중복적재/Request Collapsing 과정을 추가하였기에 당연히 성능적인 이점이 존재하겠지만, 데이터를 가져오는 과정 그 자체만을 보았을때는 의문점이 들었습니다). 이게 데이터를 추출하는 과정이 다르기에, 동일한 key로 운용하면 실무적으로 로직이나 key관리방안이 복잡해져서 관리의 효율화를 위해 나누는 것일까요(즉, 기존과 달리 분산락도 사용하고 중복적재를 방지하기 위해 이용하므로 목적 자체가 다르기에 key 포맷 및 캐싱을 별도로 운용하는 것으로 이해하는 것이 적절한지)? 제가 캐싱의 목적 부터 잘못 이해하고 있는 것일 수 있고, 실무적으로 비용/성능적 유리하다는 의미가 무엇인지 잘못 이해하고 있는 것일 수 있다고 생각하고 있습니다. 그렇기에 챗지피티에게 물어보기보다는 실무적으로 경험이 풍부하시고 그만큼 검증된 선생님의 판단이 더 정확하고 궁금하여 질문드리게 되었습니다. 바쁘신데도 항상 성심성의껏 답변해주시는 선생님께 감사의 말씀 드립니다. 이효균 드림.
consumer 쪽에서 이메일 발송 로직 대신 Thread.sleep(3000) 을 써주셨는데요. 이 말은, consumer 쓰레드 자체에서 이메일 보내는 로직을 실행한다고 가정해서 그런거라고 이해했습니다. 개인적으로 consumer 는 message 를 consume 만 하고, 실제 비즈니스 로직 (email send) 는 별개의 쓰레드로 async 하게 동작하는게 더 효율적이라고 생각이 되는데요. email 발송 로직을 별개의 쓰레드로 할 때와 현재처럼 consumer 쓰레드에서 할 때 차이점 및 주의해야할 점 (ex. offset 수동 커밋 등) 이 있을까요?
concurrency 관련하여 궁금한점이 있습니다. 하나의 topic (ex. email.send) 에 5개의 파티션이 있다고 가정. 같은 groupId 로 지정된 consumer 2 (A, B)개가 있고 각각 concurrency=3 으로 설정이 되어 있다고 가정 이런 경우, 같은 groupId 내의 컨슈머는 같은 partition 을 consume 할 수 없으니 1개의 thread 는 동작하지 않게 된다고 보면 될까요? A-1 thread ===> partition 1 A-2 thread ===> partition 2 A-3 thread ===> partition 3 B-1 thread ===> partition 4 B-2 thread ===> partition 5 B-3 thread (동작안함) ====> x 추가로 실무에서는 일반적으로 concurrency 옵션을 사용하는지 궁금합니다.