안녕하세요 60jong님! 말씀하신 것처럼 가짓수가 적은 status 의 단독 인덱스는 효과가 제한적일 수 있습니다. 사실 현재 코드에는 status 로 조회하는 쿼리가 없어 꼭 필요한 인덱스도 아니라, status 는 인덱스에서 빼도 될것같네요. 발견해주셔서 감사합니다. 다만 근본적 질문으로 다시 돌아가보면, 상태가 3가지라고 해서 해당 인덱스가 반드시 필요없는 건 아닙니다. 예를 들어, 특정 상태가 전체의 1%라면 그 상태를 조회할 때는 인덱스가 도움이 될 수 있습니다. 조회 범위를 크게 줄일 수 있으니까요. 또 다른 예시로는 아래 쿼리를 생각해볼 수 있습니다. WHERE status = 'ISSUED' ORDER BY created_at ASC LIMIT 20 여기에 만약 (status, created_at) 복합 인덱스를 걸면 효과를 볼 수 있는데요. status = 'ISSUED' 인 행들에 대해 created_at 가 이미 정렬되어 있어서, 해당 복합인덱스로 빠르게 앞에서 20개만 읽어서 조회할 수 있습니다. 그냥 지나칠 수도있는데, 이런 부분까지 잘 잡아주셔서 감사합니다. 답변이 되었기를 바랍니다. 🙇♂️
안녕하세요 드루와라잇님! 실무에서는 RPS를 임의로 정하기보다, 평소 최대 트래픽과 이벤트 때 예상되는 요청량을 기준으로 잡아요. 예를 들어, 쿠폰이벤트 오픈 직 후, 1만 명 유저가 10초 정도 안에 몰릴 것으로 예상한다면, 우선 1,000 RPS를 기준으로 테스트할 수 있습니다. 보통은 예상치 하나만 테스트하지 않고 1,000, 2,000, 5,000 RPS처럼 부하를 단계적으로 높여봅니다. (예상 트래픽보다 좀 더 버퍼를 두어서 넉넉하게 테스트하면 좋으니까요) 그러면서 응답 시간과 오류율이 갑자기 증가하는 지점을 찾고, 예상 최대 트래픽에 재시도 처리 등을 고려한 여유까지 확보했는지 확인합니다. 따라서 강의의 1,000 RPS와 5,000 RPS는 각각 예상 부하와 트래픽 급증 상황을 비교하기 위한 조건으로 참고해주시면 될 것 같습니다. 좋은 질문 감사드립니다.
안녕하세요 이찬원님! 좋은질문 감사드립니다. 예리하게 잘 잡아주셨네요. 말씀하신 대로 현재 코드는 Redis 차감 후 Kafka 발행 전에 장애가 발생하면 이벤트가 유실될 수 있습니다. 따라서 신뢰성이 높은 구조를 만들기 위해, 여러 방법을 떠올려볼 수 있을 것 같아요. 첫번째로는 Redis 에서도 Outbox 패턴처럼 사용하는 방법을 사용하는 것입니다. Lua → Redis Stream → Connector → Kafka Topic 같은 방식으로, Lua 스크립트(원자연산) 안에서 재고 차감 후, Redis Stream 으로 이벤트발행을 함께 실행합니다. 그리고 별도 워커나 Connector가 Stream을 읽어 Kafka에 발행합니다. 커넥터가 발행에 실패하거나 워커가 중단되면 메시지가 미처리 상태로 남으므로, 다시 처리할 수 있습니다. (at-least-once 등 여러 옵션을 지원합니다.) 다만, 이 구조는 본 환경에서 검증이 필요하며, Redis에는 AOF와 복제를 적용하고, 처리되지 않은 Stream 메시지가 삭제되지 않도록 보존 정책도 관리해야 합니다. Redis 에 대한 관리포인트도 늘어납니다. 두번째로는, 5단원 정합성에서 다루게 되는 대사입니다. (벌써 3단원이니 곧 만나실 것 같습니다.) 주기적으로 Redis 와 DB 쿠폰발급 데이터를 점검하여 데이터를 바로잡는 방식인데요. 해당 단원에서도 위의 케이스는 저희가 다루지는 않지만, 위 문제의 경우 예를들어 이벤트가 재발행되도록 하는 조치를 해볼 수 있을 것 같습니다. 사실 이런 문제를 해결하는 건 명확한 답이 있다기 보다는, 그 상황에 맞는 적절한 방법을 선택해서 구현하게 됩니다. 그래서 이 외에 다른 더 좋은 방법이 있을 수도있어요. 찬원님 질문덕에 저도 여러가지를 떠올려보다가 괜찮은 방법을 한번 답변드렸고, 실무라면 결국에는 좀 더 관리하기 편하고 신뢰성있는 방법을 고를 것 같습니다! 감사합니다.
안녕하세요 바나나님! 결론부터 말씀드리면, 이 강의는 저희 강의 주제에 집중하고자 패키지 구조는 간단한 구성으로 진행하게 되었습니다. 말씀하신 구성은 domain에는 순수한 도메인 모델만 두고, (인터페이스를 둔 뒤), 엔티티와 레포지토리를 storage 패키지 하위에 두는 패턴으로 이해했습니다. 반면에, 저희는 그것과는 다른 계열을 따랐는데요. domain 안에 엔티티객체를 그대로두고 사용했습니다. 말씀해주신 엔티티 매핑 비용도 물론 고려가 필요하지만, 그보다는 나눌 이유가 굳이 없는게 더 컸습니다. Coupon이나 Issuance 엔티티가 작아 도메인객체와 엔티티분리가 꼭 필요한 상황은 아니었고, 자주 변경되는 부분도 아니었고, 복잡한 구성보다는 간단한 구조로 실습하는게 더 유리하다고 판단했기 때문입니다. 좋은 질문 감사합니다.
안녕하세요 Wyatt님! 정리 공유 감사합니다 👍👍 다만 원인만 조금 다시 생각해보자면, 우선 이 강의 설정에서는 인증정보가 필요 없습니다. base image는 public이고, jibDockerBuild는 push를 안 하니까요. 즉 키체인에서 못 가져와서 실패하는 게 아닙니다. 원인은 helper 바이너리가 Gradle 데몬의 PATH에 없는 것일 수 있습니다. Jib은 config.json에 credsStore가 있으면 public 이미지라도 일단 helper를 실행해서 물어봅니다. 이때 helper 실행 자체를 못 하면 예외가 나면서 빌드가 중단되는데요. 그래서 먼저 아래 명령을 진행해보세요. ./gradlew --stop ./gradlew jibDockerBuild IntelliJ에서 먼저 빌드하면 데몬이 IDE의 PATH를 캐시해두고 계속 재사용합니다. 이걸로 풀리면 build.gradle.kts는 안 건드리셔도 됩니다. 그래도 안 되면 조사해주신 방법 1을 쓰시면 좋을 것 같아요. 참고로 helper 바이너리는 Docker Desktop 설치 시 함께 깔리구요. 최근 Docker Desktop은 credsStore가 osxkeychain 이 아니라 desktop 라서 확인해보시고 맞춰 지정해주시면 좋습니다! 좋은 공유 감사드립니다!
안녕하세요. 인호님! 좋은질문 감사합니다. 테스트 코드로도 가능합니다. 스크립트로 할 수 있는 건 보통 코드로도 할 수 있어요. 강의에서 처음 스크립트를 도입한 이유는, k6 같은 툴을 통해 부하테스트를 편하게 사용하기 위함이었어요. k6용 js 파일이 필요하니, 자연스럽게 스크립트 폴더를 만들었고 테스트에 필요한 준비도 스크립트로 같이 준비했습니다. 실무에서는 단위 및 통합 검증은 테스트 코드로 작성하고, 대량 요청이나 동시성 검증은 k6 같은 부하 테스트 도구를 활용합니다. (물론 팀마다 다르고 주관적일 수 있습니다) 강의에서는 부하테스트와 더불어 장애 발생과 데이터 정합성 확인까지 한 묶음으로 만들기 위해 별도 스크립트가 적합하다고 판단해서 사용했다고 생각하시면 됩니다. 감사합니다.
안녕하세요! 케시님, 결론부터 말하면 그 시점 코드에서는 Redis 재고가 2장 줄어들게됩니다. (1장만 줄어야 하는데) 다만, 현재 강의 시점(섹션3, 브랜치는 part-2)에서는 그렇습니다. 바로 다음 강의 (섹션4, 27강, 요청이 몰리면 API가 느려지는 진짜 이유)에서 수정하실 때, 이 부분이 해결됩니다. (part-2 Redis Lua) 1. 남은 재고를 읽는다 2. 0이면 매진으로 반환한다 3. 재고를 1 깎는다 (part-3의 Redis Lua) 1. 이 사용자가 발급받은 사용자 목록(Set)에 이미 있는지 본다. 있으면 여기서 바로 중복으로 리턴 2. 남은 재고를 읽는다 3. 0이면 매진으로 반환한다 4. 재고를 1 깎는다 5. 이 사용자를 발급받은 사용자 목록에 넣는다 part-3 에서는 Redis 원자연산에서 스크립트 하나가 통째로 실행되고, 그 사이 다른 요청이 끼어들 수 없습니다. 그래서 두 번째 요청은 1번에서 걸러져 재고 차감을 아예 타지 않습니다. 엣지케이스를 잘 생각해서 말씀해주셨네요! 좋은 질문감사합니다.
안녕하세요! 지성조님. 프리티어 EC2라면 메모리가 적어서 이런 현상이 발생하는 경우가 있습니다. 스왑 메모리를 추가한 뒤 조금 나아졌다면 메모리 부족이 원인이었을 가능성이 높습니다. 스왑 메모리로 부족한 메모리를 보충해 지금처럼 진행해보시고, 그래도 계속 같은 증상이 있다면 free -h 와 top 으로 메모리 및 CPU 사용량을 한 번 확인해 보시고, 저희 애플리케이션과 관련없는 프로세스는 제거해주시면 도움이 됩니다. 감사합니다.
안녕하세요 weogle님! docker 에서 MySQL 이 실행중인지 확인부탁드립니다. 아래 명령어로 확인할 수 있어요. docker ps MySQL (mysql-twitter) 컨테이너가 떠있어야 하고, 3306 포트가 열려있어야 합니다. 잘 떠있다면 그 후 spring boot 애플리케이션을 실행하면 잘 실행이 될 겁니다. docker run --name mysql-twitter \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=twitterdb \ -e MYSQL_USER=dev \ -e MYSQL_PASSWORD=dev123 \ -p 3306:3306 \ -d mysql:9.3 위 명령어로 도커 컨테이너를 실행할 수 있습니다. 감사합니다.☺️
안녕하세요 Aurora 님 JpaPostRepository 에 findAllPaged 메소드만 재정의한 이유는, PostRepository 에 있는 save, findAll, findById, deleteById 는 JpaRepository 의 기본 구현체가 이 메소드를 지원하는 반면, findAllPaged 는 그렇지 않기 때문에 재정의했습니다. 즉, PostRepository 라는 인터페이스를 그대로 사용하고 싶은데, JpaRepository 의 기능 추가하여 사용하고 싶어서 JpaPostRepository 라는 중간 인터페이스를 사용한 형태입니다. PostRepositoryImpl 같은 구현체를 직접 만들어서 사용하셔도 됩니다. 다만 강의의도는 위에서 말씀드린 것처럼 Spring Data Jpa 가 지원하는 JpaRepository 인터페이스를 사용하기 위함이 크다고 생각해주시면 됩니다. 감사합니다.