묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판velog., github 업로드 관련 문의강사님 잘 듣고 있습니다..! 혹시 다름이 아니라 출처를 남기고 velog, github에 공부한 내용을 업로드 해도 될까요? 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판컴파일러 경고 메세지 관련 질문입니다.안녕하세요 쿠케님 강의 잘 듣고있습니다!제네릭을 사용할 때마다 생기는 고민이 있습니다.. 제네릭을 사용하다보면 컴파일러 경고가 발생하는 경우가 있는데(IDE의 노란 밑줄) 이런 경고들을 모두 해결해서 제거해야할 까요?? 아니면 어느정도 무시를 해도 되는 걸까요??이펙티브 자바에는 런타임에 캐스팅 관련 예외가 발생할 수 있으니 이런 경고를 최선을 다해 제거하라. 라는 내용이 있기도 하고, 노란 밑줄이 보이면 눈에 거슬리기도 해서 처음에는 어느정도 제거하면서 개발을 했었는데, 시간이 지날 수록 이게 맞나 싶기도하고 책에 있는 내용이 절대적인 것도 아니기도해서 그리고 강의에서도 어느정도 무시를 하는 것 같아 질문 남깁니다!팀에서 결정한 컨벤션으로 개발하면 될 것 같은데, 다른 팀에서는 어떻게 진행하는지 궁금합니다! 최대한 제거하는지.. 어느정도는 무시(@SuppressWarnings)해도 되는지 
- 
      
        
    미해결비전공자도 이해할 수 있는 DB 설계 입문/실전관리자 테이블안녕하세요.관리자 테이블에 이메일이 유저의 이메일과 다르지 않다고 생각이 들어서 합치는 게 낫다고 생각이 들었습니다. 처음에는 유저에 그냥 합쳐서 새로운 컬럼 role로 관리하려고 했는데, 중복이 있어서관리자 테이블엔, role 컬럼을 넣어서 user, admin 2개로 추가해서 이제 유저 테이블에서 FK로 사용하려고 하는데 이 방법은 어떨까요? (강의 너무 잘 듣고 있습니다, 제 멘토이십니다ㅎㅎ) 
- 
      
        
    해결됨비전공자도 이해할 수 있는 DB 설계 입문/실전외래 키 지정은 필수가 아닌 건가요?안녕하세요. 강의 끝까지 다 들었는데 갑자기 외래 키 부분에서 궁금한 점이 생겼습니다. DB 설계할 때 테이블끼리 관계를 맺기 위해 외래 키를 지정하잖아요?그런데 외래 키로 지정을 안 하는 경우도 있나요? 조인 등에 사용될 속성은 있지만, 외래 키 지정은 안 해서 외래 키 제약 조건이 없도록 하는 경우도 있나요? 
- 
      
        
    미해결Real MySQL 시즌 1 - Part 1GAP 락에 대한 질문 드립니닷..!해당 강의와 직접적으로 연관있지는 않으나, Lock 관련하여 공부하던 중 성욱님의 MySQL GAP Lock (두번째 이야기) 글을 보게 되었습니다.우선 정말 많은 도움이 되었음에 감사드리며, 해당 글 관련하여 질문을 드리고 싶습니다. (SELECT FOR UPDATE와도 관련있는 내용이라, 이곳 게시판을 통해 질문 드립니다.)위 글의'왜 supremum 레코드를 잠그나요 ?' 단락에서, 8, 9 번째 과정을 보면 다음과 같습니다.8. 다음 페이지에서 id=137 보다 큰 (첫번째) 레코드인 id=138 읽기9. WHERE 조건에 일치하지 않으므로 잠금 걸지 않음그런데 그 아래에서 다음과 같은 내용을 말씀해 주십니다.'REPEATABLE-READ 격리 수준을 사용하는 MySQL 서버에서는 검색하고 스캔했던 인덱스 레코드를 잠근다는 아주 기본적인 규칙 때문에 잠금이 발생하고 있었던 것이죠. 사용자 데이터가 아닌 시스템 레코드(supremum)까지도 말이죠.'그러나 이 설명에 따르면, '왜 supremum 레코드를 잠그나요 ?' 에서 언급한 동작에 모순이 발생하는 것 같습니다.5,6,7 번 과정에서는 supremum record를 읽었기에 잠갔음에도 불구하고,8, 9번의 과정에서는 id=138 읽었지만 잠그지 않고 종료했기 때문입니다.위 내용이 모순된 것이라 가정하고,왜 supremum record에 Lock이 걸리도록 동작했는지에 대한 이유를 개인적으로 찾아보고 공부한 내용들에 기반하여 추측해 보았습니다. (아직 내용을 완벽히 공부하지 못해 틀린 내용이 있을 수 있습니다. 혹시 틀린 부분이 있다면 지적해주시면 감사하겠습니다!)MySQL 의 공식 문서의 내용에 따르면, Gap Lock은 Gap Lock 의 기준이 되는 index record 이전의 간격을 잠급니다.A next-key lock on an index record also affects the “gap” before that index record- MySQL 공식문서 - Next Key Lock그러나 Gap락이 특정 index record 이전의 간격을 잠근다는 위 설명에 따르면, 어떤 index의 가장 마지막 record 이후의 간격을 잠글 수 있는 방법이 없어집니다. (Gap Lock 은 이전 간격을 잠그는 것이므로)따라서, 마지막 record 이후의 값을 잠글 수 있기 위해 supremum pseudo record 를 두었을 것이라 예상합니다.supremum pseudo record(인덱스가 가질 수 있는 가상의 가장 큰 값) 에 대한 GAP Lock은 supremum pseudo record 이전의 간격들을 잠그기 때문입니다.이는 공식 문서의 다음과 같은 내용을 통해 뒷받침할 수 있을 것 같습니다.'Suppose that an index contains the values 10, 11, 13, and 20. The possible next-key locks for this index cover the following intervals, where a round bracket denotes exclusion of the interval endpoint and a square bracket denotes inclusion of the endpoint:'(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)For the last interval, the next-key lock locks the gap above the largest value in the index and the “supremum” pseudo-record having a value higher than any value actually in the index. The supremum is not a real index record, so, in effect, this next-key lock locks only the gap following the largest index value.- MySQL 공식문서 - Next Key Lock이를 바탕으로 '왜 supremum 레코드를 잠그나요 ?' 의 원인을 분석해보면 다음과 같습니다.- 우선 3,4 번 과정에서 id=137 인 index record를 읽습니다. - 이때 id는 PK 이므로 값이 유니크하기에, BETWEEN 136 AND 137 조건을 통해 137이라는 값을 찾았으면, 해당 값이 조건을 만족하는 가장 마지막 값이라는 것을 알 수 있습니다.- 따라서 id=138 을 더 이상 탐색하지 않고 종료하게 되는데, 이때 id=137은 해당 PK 인덱스(리프노드 페이지) 의 가장 마지막 값이므로, supremum 레코드를 이용하여 추가적인 간격을 잠구는 것이지 않을까 하는 생각이 들었습니다.위 부분에 대한 성욱님의 의견을 듣고 싶어 질문드렸습니다.---이와 별개로, 추가 궁금증이 있습니다.1. MySQL 공식 문서에서도 언급되었던 InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. 라는 문장에서 검색되거나 스캔된 의 의미가 잘 이해되지 않습니다.예를 들어, id를 PK로 갖는 어떤 테이블에서, id = 3 인 row 를 탐색하기 위해서는,B-tree의 root 부터 시작 ~ leaf 노드로 이동 후, leaf node의 페이지의 전체 데이터를 스캔해야 하지 않나요?예를 들어 id=3 인 record 가 들어있는 leaf 노드의 페이지에 id가 1 ~ 5 까지 있다고 하면,leaf 노드의 페이지로 진입하여 1부터 순차 탐색을 진행할 것 같은데, 그렇게 되면 1~5 까지의 index 값 사이에서 3을 탐색하는 데 1과 2를 먼저 찾을 것이므로, id가 1, 2, 3인 row가 모두 잠겨야 하지 않나 하는 생각이 들었습니다. 혹시 공식문서에서 언급한 검색 혹은 스캔된의 의미가, 조건에 사용된 인덱스가 걸린 필드 값만 확인하는 것은 포함하지 않는 것일까요?(ex - 위 예시에서 id가 1인 인덱스 확인조건에 부합되지 않으므로 넘어감. (스캔되었다고 보지 않음)id가 2인 인덱스 확인조건이 부합되지 않으므로 넘어감id가 3인 인덱스 확인조건에 부합되므로 나머지 값 읽어옴.)위 내용들에서, 제가 어떤 부분을 잘못 생각하고 있는지 알 수 있을까요?정말 좋은 강의와 글 공유해 주셔서 감사합니다. 
- 
      
        
    미해결비전공자도 이해할 수 있는 DB 설계 입문/실전게시판 닉네임, 아이디 관련질문안녕하세요. 수업 듣다가 궁금한 사항이 있어 질문 드립니다!중요하지 않을 수도 있는데, 사용자 테이블에서 닉네임이랑 아이디를 따로 해야 하지 않나요?UI 이미지보면 jscode123 이랑 마이페이지에 petya는 다른 것처럼 보이는데 사용자 테이블에 닉네임으로 해도 괜찮을까요? 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판sqlExceptionHelper null 문제createTest 실행시 오류 발생 createTest에서 500 오류가 떠서 보니 스프링애플리케이션을 실행하면java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(java.sql.SQLException, String)" because the return value of "org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.sqlExceptionHelper()" is null at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:116) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:290) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:123) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:77) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.boot.model.relational.Database.<init>(Database.java:45) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:221) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:189) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:171) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1431) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1502) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.1.11.jar:6.1.11] 2025-03-05T23:27:34.720+09:00 WARN 50007 --- [jamm-board-article-service] [ main] org.hibernate.orm.deprecation : HHH90000025: MySQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) 2025-03-05T23:27:35.162+09:00 INFO 50007 --- [jamm-board-article-service] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) 2025-03-05T23:27:35.163+09:00 INFO 50007 --- [jamm-board-article-service] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2025-03-05T23:27:35.564+09:00 INFO 50007 --- [jamm-board-article-service] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 9000 (http) with context path '/' 2025-03-05T23:27:35.570+09:00 INFO 50007 --- [jamm-board-article-service] [ main] jamm.board.article.ArticleApplication : Started ArticleApplication in 3.381 seconds (process running for 3.717) 다음과 같은 오류가 뜨네요 ㅜ 제가 폴더명을 다르게 했는데 문제가 있는 걸까요 어떤 게 문제인지 잘 모르겠습니다. 분명히 도커로 mysql도 띄었고 jdbc 연결이 안된걸까요 server.port: 9000 spring: application: name: jamm-board-article-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/article username: root password: root jpa: database-platform: org.hibernate.dialect.MySQLDialect open-in-view: false show-sql: true hibernate: ddl-auto: none logging: level: org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql.BasicBinder: TRACE public class ArticleApiTest { RestClient restClient = RestClient.create("http://localhost:9000"); @Test void createTest() { ArticleResponse response = create(new ArticleCreateRequest( "hi", "my content", 1L, 1L )); System.out.println("response = " + response); } ArticleResponse create(ArticleCreateRequest request) { return restClient.post() .uri("/v1/articles") .body(request) .retrieve() .body(ArticleResponse.class); } @Getter @AllArgsConstructor static class ArticleCreateRequest { private String title; private String content; private Long writerId; private Long boardId; } @Getter @AllArgsConstructor static class ArticleUpdateRequest { private String title; private String content; } } application.yml (article)@RestController @RequiredArgsConstructor public class ArticleController { private final ArticleService articleService; @GetMapping("/v1/articles/{articleId}") public ArticleResponse read(@PathVariable Long articleId) { return articleService.read(articleId); } @PostMapping("/v1/articles") public ArticleResponse create(@RequestBody ArticleCreateRequest request) { return articleService.create(request); } @PostMapping("/v1/articles/{articleId}") public ArticleResponse update(@PathVariable Long articleId, @RequestBody ArticleUpdateRequest request) { return articleService.update(articleId, request); } @DeleteMapping("/v1/articles/{articleId}") public void delete(@PathVariable Long articleId) { articleService.delete(articleId); } } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' implementation project(':common:snowflake') implementation project(':common:outbox-message-relay') implementation project(':common:event') } rootProject.name = 'jamm-board' include 'common' include 'common:snowflake' include 'common:data-serializer' include 'common:event' include 'common:outbox-message-relay' include 'service' include 'service:article' include 'service:comment' include 'service:view' include 'service:like' include 'service:hot-article' include 'service:article-read' docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea2f15b15cee mysql:8.0.38 "docker-entrypoint.s…" About an hour ago Up 4 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp kuke-board-mysql..2025-03-05 23:02:20 2025-03-05T14:02:20.281045Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.38' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL. 학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 
- 
      
        
    미해결입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기클래스 생성 강의 수강 중 오류 발생따라 적었는데 프로퍼티 id가 한번도 사용되지 않았다는 오류가 떠요.. 
- 
      
        
    미해결중고급 SQL과 실전 데이터 분석 101 문제 풀이28번 문제 답에대해서 질문드립니다.강의를 보기전에 혼자 쿼리를 작성하고 강의를 보았는데 강사님의 쿼리와 다른 방식으로 작성을 하여서 질문 합니다!.WITH 절을 사용하여 다음과 같이 쿼리를 작성하였는데 이런방식으로도 현업?이나 주로 작성하는지 궁굼합니다. 제가 작성한 쿼리는 다음과 같습니다!WITH rented_films AS ( SELECT DISTINCT I.film_id FROM rental R JOIN inventory I ON I.inventory_id = R.inventory_id)SELECT F.titleFROM film FLEFT JOIN rented_films RF ON RF.film_id = F.film_idWHERE RF.film_id IS NULL; 
- 
      
        
    미해결백엔드 개발자 성능 개선 초석 다지기ExecutorService와 CompletableFuture의 차이점과 역할안녕하세요! 비동기 코드에 관해 질문이 있습니다. ExecutorService 는 스레드를 관리하는 역할을 하는것이고 CompletableFuture 가 실제로는 비동기 요청을 처리하는 것으로 이해하였는데 맞을까요? Future 은 예외 처리를 할 수 없기 때문에 잘 쓰이기 않을까요??CompletableFuture 가 지원하는 기능이 더 많기 때문에 주로 CompletableFuture 를 사용하나요? 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판레디스의 동적 서버 리스트와 샤딩 범위 중복 문제 질문안녕하세요, 강의 잘 듣고 있습니다. ^^분산 시스템에서 균등 샤딩 부분을 듣다가 궁금한 점이 생겨서 질문드립니다.각 서버는 자신의 @Scheduled에 의해 레디스 서버로 조회하여 자신의 인덱스를 찾은 후 이를 기반으로 각자 서버 로컬에서 샤딩 범위를 계산하는 것으로 이해했는데요. 여기서 궁금한 점은 각 서버마다 @Scheduled가 동작하는 시점도 다를테고, 레디스의 저장된 서버 리스트가 동적으로 변하기도 하는데, 이러한 이유로 서로 다른 서버가 부분적으로 겹치는 샤딩 범위를 각자 계산하여 카프카로 중복 전송하는 시나리오가 발생하지는 않는 것인가요?ex) A의 @Scheduled 동작 시점에서는 서버가 3개 뿐이었지만 B의 @Scheduled 동작 시점에는 서버가 5개로 늘어난 경우서버 A가 계산한 자신의 샤딩 범위 0~20서버 B가 계산한 자신의 샤딩 범위 13 ~ 27 답변 주시면 감사하겠습니다.😊 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판좋아요 비관적 락 적용 시 데드락 발생 관련 질문입니다..spring boot: 3.3.2mysql: 8.0.38강사님과 동일한 버전 사용하고 있습니다.@Test void likePerformanceTest() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); likePerformanceTest(executorService, 1111L, "pessimistic-lock-1"); likePerformanceTest(executorService, 2222L, "pessimistic-lock-2"); // likePerformanceTest(executorService, 3333L, "optimistic-lock"); } private void likePerformanceTest(ExecutorService executorService, Long articleId, String lockType) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3000); System.out.println(lockType + " start"); long start = System.currentTimeMillis(); for (int i = 0; i < 3000; i++) { long userId = i + 2; executorService.submit(() -> { like(articleId, userId, lockType); latch.countDown(); }); } latch.await(); long end = System.currentTimeMillis(); System.out.println("lockType = " + lockType + ", time = " + (end - start) + "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); }위 테스트를 실행 시키면pessimistic-lock-1 start lockType = pessimistic-lock-1, time = 5353ms pessimistic-lock-1 end count = 2991 pessimistic-lock-2 start lockType = pessimistic-lock-2, time = 6667ms pessimistic-lock-2 end count = 2991count 가 3001이 안나오길래 서버 로그를 확인했는데,com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction이렇게 데드락이 발생됐다는 로그가 출력됐더라구요.. 강사님과 똑같은 코드로 실행한거 같은데, 왜 데드락이 발생한걸까요??의심되는 부분은 @Transactional public void likePessimisticLock1(Long articleId, Long userId) { articleLikeRepository.save( ArticleLike.create(snowflake.nextId(), articleId, userId) ); int result = articleLikeCountRepository.increase(articleId); if (result == 0) { articleLikeCountRepository.save( ArticleLikeCount.init(articleId, 1L) ); } }여러 스레드가 동시에 result == 0 조건에 진입해서 동일한 article_id 를 insert 하려고 하다가 충돌되는 과정에서 데드락이 발생된 것 같은데... 정확한 원인이 뭘까요? 제가 잘못 친게 있어서 그런걸까요..? PessimisticLock1, PessimisticLock2 둘 다 데드 락이 발생한 원인이 궁금합니다!게시물 생성 시점에 미리 0으로 count를 생성 해놓으면 괜찮을 것 같은데, 같은 코드로 실행했는대도 결과가 달라 질문남깁니다.. 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판레디스와 관련해서 궁금한 점이 있습니다.안녕하세요 쿠케님 좋은 강의 감사합니다. 레디스와 관련되어 궁금한 점이 있어 여쭙고자 합니다. 현재 사내에서는 레디스를 사용 중이 아닌데 최근 JWT 기반의 인증 인가를 주니어들끼리 도입해서 이에 따른 토큰 관리 저장소로 레디스 도입을 제안 했는데요. 레디스를 이미 사용하는 환경에서 레디스를 직접 EC2에 설치해서 사용하는지 아니면 엘라스틱 캐시와 같은 클라우드 서비스에서 제공하는 것을 사용하는지 궁금합니다.레디스와 관련되어서 몇 가지 기술 토론을 본 적이 있는데 레디스를 굳이 클러스터링 해야하나? 레디스가 단순한 이론서에서 설명하는 것 만큼 레디스 한 대가 금방 뻗을 만큼 취약하지 않다는 입장도 보았는데... 레디스 도입에 따른 클러스터링은 필수적인가요? 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판좋아요 락 질문입니다.안녕하세요 비관적 락, 낙관적 락부분 수업 듣다가 테스트 결과값이 이상해서 질문드립니다.이렇게 나오는데 비관적 락1에서 count가 0이 나오는데 코드가 잘못된 걸까요? 다시 살펴봤는데 이상한 부분은 딱히 없어서 왜 이렇게 나오는지 몰라서 질문드립니다! 
- 
      
        
    미해결[리뉴얼] 처음하는 SQL과 데이터베이스(MySQL) 부트캠프 [입문부터 활용까지]데이터 삭제 질문- 강의 영상에 대한 질문이 있으시면, 상세히 문의를 작성해주시면, 주말/휴일 제외, 2~3일 내에 답변드립니다 (이외의 문의는 평생 강의이므로 양해를 부탁드립니다.)- 강의 답변이 도움이 안되셨다면, dream@fun-coding.org 로 메일 주시면 재검토하겠습니다. - 괜찮으시면 질문전에 챗GPT 와 구글 검색을 꼭 활용해보세요~- 잠깐! 인프런 서비스 운영(다운로드 방법포함) 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요. 즐겁게 강의를 수강 중인 수강생입니다. sql 데이터 수정 삭제 문법 이해하기 - 실습 중이름이 김철수인 학생만 삭제하려고 넣으니15:25:41 DELETE FROM students WHERE name = '김철수' Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences -> SQL Editor and reconnect. 0.000 sec 라고 오류가 떠 gpt에 질문하여 해결하였습니다. 다만 gpt는 안전모드를 해제(일시적 또는 영구적)하거나 기본키(id) 또는 인덱스가 있는 컬럼을 사용하여 삭제하라고 추천합니다. 질문은, 보통 sql 이용 시 안전모드를 해제하고 사용하면 되는 걸까요?아니라면 id가 아닌 컬럼을 선택하여 삭제하는 경우가 거의 없어서 일시적으로 안전모드 해제하고 삭제하는 것이 일반적인 경우일까요? 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판게시판 CRUD 관련 오류학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요,저와 비슷한 오류가 많은 것 같은데 해결이 안된것 같아서 질문드립니다.13분쯤에 코드를 같이 따라 쳐보도 위와 같은 오류가 발생합니다. 이문제가 해결이 안되서 코드만 3번째 다시 치고 있습니다 ㅠㅠ... 500 Internal Server Error: "{"timestamp":"2025-03-03T03:54:49.747+00:00","status":500,"error":"Internal Server Error","path":"/v1/articles"}" org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Internal Server Error: "{"timestamp":"2025-03-03T03:54:49.747+00:00","status":500,"error":"Internal Server Error","path":"/v1/articles"}" at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:102) at org.springframework.web.client.StatusHandler.lambda$defaultHandler$3(StatusHandler.java:89) at org.springframework.web.client.StatusHandler.handle(StatusHandler.java:146) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.applyStatusHandlers(DefaultRestClient.java:698) at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:200) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.readBody(DefaultRestClient.java:685) at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.body(DefaultRestClient.java:631) at kuke.board.article.api.ArticleApiTest.create(ArticleApiTest.java:29) at kuke.board.article.api.ArticleApiTest.createTest(ArticleApiTest.java:16) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) https://velog.io/@ghwns9991/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-3.2-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EC%9D%B4%EB%A6%84-%EC%9D%B8%EC%8B%9D-%EB%AC%B8%EC%A0%9C 스프링 3.2 부터 uri 관련 매개변수 어노테이션을 잘 인식하지 못한다고 하나 봅니다. 저는 윗 글의 두 번째(-parameters) 방법으로 해결했습니다. 위와 같이 해결했다고 하는 문구가 많은데,저는 -parameters를 추가해도 같은 오류가 발생합니다. 혹시 추가 설정이 필요한 부분이나 추가적으로 말씀해주시면 업로드 하겠습니다 ! 코드 추가 ArticleControllerpackage kuke.board.article.controller; import kuke.board.article.service.ArticleService; import kuke.board.article.service.request.ArticleCreateRequest; import kuke.board.article.service.request.ArticleUpdateRequest; import kuke.board.article.service.response.ArticleResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor public class ArticleController { private final ArticleService articleService; @GetMapping("/v1/articles/{articleId}") public ArticleResponse read(@PathVariable Long articleId) { return articleService.read(articleId); } @PostMapping("/v1/articles") public ArticleResponse create(@RequestBody ArticleCreateRequest request) { return articleService.create(request); } @PutMapping("/v1/articles/{articleId}") public ArticleResponse update(@PathVariable Long articleId, @RequestBody ArticleUpdateRequest request) { return articleService.update(articleId, request); } @DeleteMapping("/v1/articles/{articleId}") public void delete(@PathVariable Long articleId) { articleService.delete(articleId); } } entity-Article package kuke.board.article.entity; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import java.time.LocalDateTime; @Table(name = "article") @Getter @Entity @ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Article { @Id private Long articleId; private String title; private String content; private Long boardId; private Long writerId; private LocalDateTime createAt; private LocalDateTime modifiedAt; public static Article create(Long articleId, String title, String content, Long boardId, Long writerId) { Article article = new Article(); article.articleId = articleId; article.title = title; article.content = content; article.boardId = boardId; article.writerId = writerId; article.createAt = LocalDateTime.now(); article.modifiedAt = article.createAt; return article; } public void update(String title, String content) { this.title = title; this.content = content; modifiedAt = LocalDateTime.now(); } } repository - ArticleRepositorypackage kuke.board.article.repository; import kuke.board.article.entity.Article; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ArticleRepository extends JpaRepository<Article, Long> { } Service - request - ArticleCreateRequestpackage kuke.board.article.service.request; import lombok.Getter; import lombok.ToString; @Getter @ToString public class ArticleCreateRequest { private String title; private String content; private Long writerId; private Long boardId; }Service - request - ArticleUpdateRequestpackage kuke.board.article.service.request; import lombok.Getter; import lombok.ToString; @Getter @ToString public class ArticleUpdateRequest { private String title; private String content; } Service - response - ArticleResponsepackage kuke.board.article.service.response; import kuke.board.article.entity.Article; import lombok.Getter; import lombok.ToString; import java.time.LocalDateTime; @Getter @ToString public class ArticleResponse { private Long articleId; private String title; private String content; private Long boardId; private Long writerId; private LocalDateTime createAt; private LocalDateTime modifiedAt; public static ArticleResponse from(Article article) { ArticleResponse response = new ArticleResponse(); response.articleId = article.getArticleId(); response.title = article.getTitle(); response.content = article.getContent(); response.boardId = article.getBoardId(); response.writerId = article.getWriterId(); response.createAt = article.getCreateAt(); response.modifiedAt = article.getModifiedAt(); return response; } }ArticleApplicationpackage kuke.board.article; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ArticleApplication { public static void main(String[] args) { SpringApplication.run(ArticleApplication.class, args); } }resources - application.ymlserver: port: 9000 spring: application: name: kuke-board-article-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/article username: root password: root jpa: database-platform: org.hibernate.dialect.MySQLDialect open-in-view: false show-sql: false hibernate: ddl-auto: none test - java - kuke.board.article.apipackage kuke.board.article.api; import kuke.board.article.service.request.ArticleCreateRequest; import kuke.board.article.service.response.ArticleResponse; import lombok.AllArgsConstructor; import lombok.Getter; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; import org.springframework.web.client.RestClient; public class ArticleApiTest { RestClient restClient = RestClient.create("http://localhost:9000"); @Test void createTest() { ArticleResponse response = create(new ArticleCreateRequest( "hi", "my contents", 1L, 1L )); System.out.println("response = " + response); } ArticleResponse create(ArticleCreateRequest request) { return restClient.post() .uri("/v1/articles") .body(request) .retrieve() .body(ArticleResponse.class); } @Test void readTest() { ArticleResponse response = read(1234L); System.out.println("response = " + response); } ArticleResponse read(Long articleId) { return restClient.get() .uri("/v1/articles/{articleId}") .retrieve() .body(ArticleResponse.class); } @Test void updateTest() { update(1234L); ArticleResponse response = read(1234L); System.out.println("response = " + response); } void update(Long articleId) { restClient.put() .uri("/v1/articles/{articleId}", articleId) .body(new ArticleUpdateRequest("hi 2", "my content 22")) .retrieve() ; } @Test void deleteTest() { restClient.delete() .uri("/v1/articles/{articleId}", 1234L) .retrieve(); } @Getter @AllArgsConstructor static class ArticleCreateRequest { private String title; private String content; private Long writerId; private Long boardId; } @Getter @AllArgsConstructor static class ArticleUpdateRequest { private String title; private String content; } }article - build.gradle dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' implementation project(':common:snowflake') } 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판PathVariable과 RequestParam, Test Code에 관하여안녕하세요 쿠케님 강의 잘 듣고 있습니다. 너무 좋아서 팀원들한테도 전파했을 정도입니다. 다들 구매예정이시라고 하네용 강의 듣는 도중 궁금점이 생겼는데 PathVariable과 RequestParam의 가장 큰 차이점은 단 하나의 유일한 자원의 식별자를 url 경로에서 표현하는가의 여부로 배웠습니다. 그래서 저는 articleId의 경우는 PatVariable로 남겨두었습니다. 이 두가지를 구분하는 쿠케님의 기준이 있으실까용?이 강의에서 테스트는 전부 JUnit이 아닌 다른 것들로 수행 중이신데요. 실제로 테스트 코드 작성하실때도 JUnit을 사용하지 않으시나요? 아니면 그냥 강의 진행 편의상 JUnit을 사용하지 않으신건가요? 감사합니다. 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판좋아요 동시성 로직 관련 질문안녕하세요 강사님, 좋은 강의 잘 듣고 있습니다. 바쁘실텐데도 불구하고 답변해주셔서 감사합니다. 질문은 크게 2가지 입니다.유니크 제약 조건 위반시 예외처리방법like() 로직에선 article_id와 user_id로 유니크 제약 조건을 걸어서 한 명의 사용자가 중복 생성하는 것을 막고 있습니다. 이 유니크 제약 조건을 위반하여 발생하는 에러는 @ControllerAdvice에서 @ExceptionHandler(DataIntegrityViolationException.class) 로 시작하는 핸들러를 통해서 처리하는 것이 일반적인가요? 2.unlike()에서 한 명의 사용자가 삭제 중복요청시 발생하는 동시성 문제 @Transactional public void unlikePessimisticLock2(Long articleId, Long userId) { articleLikeRepository.findByArticleIdAndUserId(articleId, userId) .ifPresent(articleLike -> { articleLikeRepository.delete(articleLike); ArticleLikeCount articleLikeCount = articleLikeCountRepository.findLockedByArticleId(articleId).orElseThrow(); articleLikeCount.decrease(); }); }한 명의 사용자가 "좋아요" 중복 생성 요청하는 것을 방어하는 로직이 있으니 "좋아요 해제" 중복 요청에 대한 고려에 대해서 생각해봤습니다. 이 로직에선 findLockedByArticleId 에서 베타락을 걸게 되어서 하나의 article에 대한 여러 사용자의 "좋아요 해제" 요청시의 동시성 문제를 방어하고 있습니다. 그런데 한 명의 사용자가 "좋아요 해제" 중복 요청 시에 ifPresent 구문을 통과하게 되어 articleLikeRepository.delete(articleLike);를 여러번 수행하게 됩니다. 여기서 delete는 멱등성이 보장되니 큰 문제 없이 넘어가겠지만 그 아래 count감소 로직은 해당 트랜잭션들이 베타락을 획득할 때마다 계속 수행되는 것이 아닌지 궁금합니다. 일단 ifPresent 구문 안으로만 들어오면 count감소는 무조건 일어나게 되니 문제가 생기는게 아닐까요? 그래서 ifPresent구문 전에 락을 걸기 위해 findByArticleIdAndUserId() 여기서 락획득이 이루어져야 하는건 아닌지 궁금합니다. 작성하다 보니 질문이 길어져서 죄송합니다. 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판CommentServiceTest에서 테스트 오류가 나요.학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. [문의사항]강사님의 코드 그대로 따라서 하나하나 작성하고, 모든 부분의 설정을 똑같이 했습니다. Comment 테스트 중에 <삭제할 댓글이 자식 있으면, 삭제 표시만 한다.> 는 성공이 되었습니다.그러나 <하위 댓글이 삭제되고, 삭제되지 않은 부모면, 하위 댓글만 삭제한다.> 부분의 테스트를 그대로 따라했는데, mockito.exceptions.misusing.UnnecssaryStubbingException 오류가 납니다.강의 자료로 올려주신 소스코드를 그대로 복사하여 붙여넣기 해도 같은 오류가 납니다. 소스코드와 에러메세지 작성드리며, 같은 환경에서 똑같은 소스코드를 작성했는데 어떻게 오류가 나는지 궁금합니다. 해결 부탁드립니다 ㅠㅠㅠpackage kuke.board.comment.service; import kuke.board.comment.entity.Comment; import kuke.board.comment.repository.CommentRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class CommentServiceTest { @InjectMocks CommentService commentService; @Mock CommentRepository commentRepository; @Test @DisplayName("삭제할 댓글이 자식 있으면, 삭제 표시만 한다.") void deleteShouldMarkDeletedIfHasChildren() { // given Long articleId = 1L; Long commentId = 2L; Comment comment = createComment(articleId, commentId); given(commentRepository.findById(commentId)) .willReturn(Optional.of(comment)); given(commentRepository.countBy(articleId, commentId, 2L)).willReturn(2L); // when commentService.delete(commentId); // then verify(comment).delete(); } @Test @DisplayName("하위 댓글이 삭제되고, 삭제되지 않은 부모면, 하위 댓글만 삭제한다.") void deleteShouldDeleteChildOnlyIfNotDeletedParent() { // given Long articleId = 1L; Long commentId = 2L; Long parentCommentId = 1L; Comment comment = createComment(articleId, commentId, parentCommentId); given(comment.isRoot()).willReturn(false); Comment parentComment = mock(Comment.class); given(parentComment.getDeleted()).willReturn(false); given(commentRepository.findById(commentId)) .willReturn(Optional.of(comment)); given(commentRepository.countBy(articleId, commentId, 2L)).willReturn(1L); given(commentRepository.findById(parentCommentId)) .willReturn(Optional.of(parentComment)); // when commentService.delete(commentId); // then verify(commentRepository).delete(comment); verify(commentRepository, never()).delete(parentComment); } private Comment createComment(Long articleId, Long commentId) { Comment comment = mock(Comment.class); given(comment.getArticleId()).willReturn(articleId); given(comment.getCommentId()).willReturn(commentId); return comment; } private Comment createComment(Long articleId, Long commentId, Long parentCommentId) { Comment comment = createComment(articleId, commentId); given(comment.getParentCommentId()).willReturn(parentCommentId); return comment; } } 
- 
      
        
    해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판좋아요 구현 오류 질문입니다.학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.안녕하세요 혼자 해결해 보려고 3일 동안 했는데 해결을 못해서 질문 드립니다.likeAndUnlikeTest() 실행하면이런 오류가 발생합니다..디버깅 해보니여기까지는 되는데 다음에 AopUtils로 넘어가서 구현체까지 넘어가는데 어디 문제인지 모르겠습니다!..이게 테스트 코드이고DB까지 강의랑 똑같이 했습니다,, 
