안녕하세요 강의 주제와는 맞지 않는 질문일 수도 있는데 모놀리식 아키텍처에서는 그냥 게시글 생성/수정/삭제, 댓글 생성/삭제 등 이런 로직에서 점수를 업데이트하면 될 것 같아서 그런데 모놀리식 아키텍처에서는 이벤트 방식으로 인기글을 선정하는 방법은 불필요하게 복잡하게 구현하게 되는걸까요?
안녕하세요. 섹션 10에 KSQLDB와 Connect 첫 강의 1초짜리에 강의가 존재하는 이유가 강의 자료 파일 공유를 위해서 있는 것으로 유추되는데 아무것도 없는 빈 리스트네요. 혹시 제가 뭔가 놓치거나 강의 업데이트에 이슈가 있었던 것은 아닌지 여쭤보고자 게시글 남깁니다. 참고로 강의 처음에 공유해주신 강의 자료 PPT에는 해당 섹션에 대한 장표가 누락돼 있습니다. 감사합니다.
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 같은 Consumer groups에 속한 consumer들은 각각 다른 partition을 구독하는데, 같은 offset을 사용해야하는 이유가 잘 이해가 헷갈립니다. 즉, Consumer groups에 속한 consumer들끼리 같은 offset을 사용하는 이유가 잘 이해가 되질 않습니다 "각 데이터는 각 Topic의 Partition 단위로 순차적으로 기록되고, 기록된 데이터는 offset을 가진다." 라는게 간단한 예시를 들면, Topic1 ( 날씨에 관한 데이터 ) Partition 1 ( 서울 날씨 관한 데이터) partition 2 ( 부산 날씨 관한 데이터) partition 3 ( 대구 날씨 관한 데이터) 라고 이해를 했습니다. 근데 여기서 partition마다 데이터가 다른데 같은 offset을 사용한다는 개념이 잘 와닿지가 않습니다 항상 강의 잘 보고 있습니다. 감사합니다
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요. 강의 잘 듣고 있습니다. 테스트 코드 update와 delete 메서드를 실행해도 변화가 없어서 검색을 해보니 .retrieve() 뒤에 .toBodilessEntity()를 추가하니 정상적으로 작동됩니다. 강의와 다르게 .toBodilessEntity() 메서드를 추가해야지 작동하여서 이에 대해 어떤 차이가 있는 궁금합니다.
안녕하세요 강의 잘 듣고 있습니다. MSA 환경에서 SPRING CLOUD GATEWAY를 사용할때 예를들어 유저 서비스에서 JWT 서비스(생성, 검증)를 구성하고 SPRING CLOUD GATEWAY feign client로 통신하고 토큰을 검증하고 각 모듈로 헤더를 통해서 userId, role을 전달할때 api별 권한을 관리를 어떻게 하는지는 궁금합니다. 스프링 클라우드 게이트웨이에 SecurityConfig에서 모든 모듈에 대한 url에 권한을 설정하기에는 너무 많은 api별 권한을 적어야놔야 할것같아서 이게 맞나 싶습니다 혹시 각 모듈마다 ApiGatewayAuthenticationFilter를 만들어서 헤더 값을 통해서 아래 코드와 같이 SecurityContextHolder를 주입시켜서 각 모듈에 대한 SecurityConfig에서 권한을 관리 하거나 @ PreAuthorize 를 통하여 관리하거나 실무에서는 어떤 방법을 쓰는지 궁금합니다!!! List<SimpleGrantedAuthority> authorities = Collections.singletonList( new SimpleGrantedAuthority(formattedRole)); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userId, null, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); 더 좋은 방법이 있다면 알고 싶습니다!!
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요 강의 잘 듣고 있는 학생입니다. 다름이 아니라, 강의에서 query DSL 대신 Native Query를 사용하시는 이유가 있으신가요? 현업에서는 query DSL 대신 Native Query를 더 많이 사용하는걸까요??
docker exec -it bash 해서 복사 해서 내 디렉터리까지 옮겨서 다 따라해봤는데 저는 kongmac@minuuimaegbug-ui-noteubug Dockerfile % docker logs cde3e64f71aa 2025-03-07T08:00:12.394150Z 0 [System] [MY-015015] [Server] MySQL Server - start. 2025-03-07T08:00:12.622065Z 0 [Warning] [MY-010143] [Server] Ignoring user change to 'root' because the user was set to 'mysql' earlier on the command line 2025-03-07T08:00:12.623333Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 9.1.0) starting as process 1 mysqld: File './binlog.index' not found (OS errno 13 - Permission denied) 2025-03-07T08:00:12.628763Z 0 [ERROR] [MY-010119] [Server] Aborting 2025-03-07T08:00:12.630606Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 9.1.0) MySQL Community Server - GPL. 2025-03-07T08:00:12.630620Z 0 [System] [MY-015016] [Server] MySQL Server - end. 이런 오류가 나오네요... 도커로 db포트 열었으면 어떻게 해야하나요?
안녕하세요 쿠케님 강의 잘 듣고있습니다! 제네릭을 사용할 때마다 생기는 고민이 있습니다.. 제네릭을 사용하다보면 컴파일러 경고가 발생하는 경우가 있는데(IDE의 노란 밑줄) 이런 경고들을 모두 해결해서 제거해야할 까요?? 아니면 어느정도 무시를 해도 되는 걸까요?? 이펙티브 자바에는 런타임에 캐스팅 관련 예외가 발생할 수 있으니 이런 경고를 최선을 다해 제거하라. 라는 내용이 있기도 하고, 노란 밑줄이 보이면 눈에 거슬리기도 해서 처음에는 어느정도 제거하면서 개발을 했었는데, 시간이 지날 수록 이게 맞나 싶기도하고 책에 있는 내용이 절대적인 것도 아니기도해서 그리고 강의에서도 어느정도 무시를 하는 것 같아 질문 남깁니다! 팀에서 결정한 컨벤션으로 개발하면 될 것 같은데, 다른 팀에서는 어떻게 진행하는지 궁금합니다! 최대한 제거하는지.. 어느정도는 무시( @SuppressWarnings )해도 되는지
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 ps CONTAINER 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 문의하기를 이용해주세요.
이번 카프카에서는 두 가지를 배운거 같은데 1. order -> catalog로 프로듀서와 컨슈머로 역할을 나눠서 재고관리 2. 두개의 포트에서 단일 데이터베이스에 접근했을 때 카프카를 이용해서 관리하는 거 이렇게 두 가지 가르쳐주신 거 같습니다. 제가 생각했을 때 1번은 카프카를 사용한 이유는 예제를 보여주기 위해서라고 생각했습니다. 카프카말고 @FeignClient를 사용해서 주문을 했을 시에 catalog에 접근하여 재고를 업데이트 시켜주면 되겠다 라고 생각했습니다. 2번은 카프카를 사용했어야 했다고 생각한 이유가 2개의 서버포트가 단일 데이터베이스에 접근했을 시에 카프카 유무에 따라 동시에 같은 주문이 들어왔을때 데이터의 일관성이 깨질수도 있다고 생각했습니다. 제가 적은 내용이 틀리면 말씀해주시면 감사하겠습니다.
재고관리에 카프카 쓴 이유가 궁금합니다. 제가 이해한 내용으로는 현재 order와 catalog는 다른 포트를 사용 중이고 같은 DB를 사용 중인걸로 알고있습니다. 그래서 카프카말고 @FeignClient로 재고관리에 업데이트 해주는 방식이 있는 거 같은데 이 상황에서 카프카를 쓴 이유가 있나요?
안녕하세요, 강의 잘 듣고 있습니다. ^^ 분산 시스템에서 균등 샤딩 부분을 듣다가 궁금한 점이 생겨서 질문드립니다. 각 서버는 자신의 @Scheduled에 의해 레디스 서버로 조회하여 자신의 인덱스를 찾은 후 이를 기반으로 각자 서버 로컬에서 샤딩 범위를 계산하는 것으로 이해했는데요. 여기서 궁금한 점은 각 서버마다 @Scheduled가 동작하는 시점도 다를테고, 레디스의 저장된 서버 리스트가 동적으로 변하기도 하는데, 이러한 이유로 서로 다른 서버가 부분적으로 겹치는 샤딩 범위를 각자 계산하여 카프카로 중복 전송하는 시나리오가 발생하지는 않는 것인가요? ex) A의 @Scheduled 동작 시점에서는 서버가 3개 뿐이었지만 B의 @ Scheduled 동작 시점에는 서버가 5개로 늘어난 경우 서버 A가 계산한 자신의 샤딩 범위 0~20 서버 B가 계산한 자신의 샤딩 범위 13 ~ 27 답변 주시면 감사하겠습니다. 😊
spring boot: 3.3.2 mysql: 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 = 2991 count 가 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를 생성 해놓으면 괜찮을 것 같은데, 같은 코드로 실행했는대도 결과가 달라 질문남깁니다..