묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기
메모리 크기
JVM 관련프로그램인데 micro인 1GB로 메모리를 설정하면 프로그램이 뻗을 일은 없을까요?(1번과 연계해서) 현재 SSR로 화면 그려주고 있는데, api처리 + 빈 컨테이너(Springboot) + Kotlin + 템플릿엔진(타임리프) 까지 했을때 1GB를 초과하지는 않으셧는지도 궁금합니다!만약에 화면을 다른 언어/라이브러리(Vue, React)로 개발하게 된다면 CORS라던지 건드려야 될게 많아지고, 추가로 node 환경을 또 띄워야 되서 메모리가 부족하게 될 수도 있을까요?
-
미해결백엔드 개발자 성능 개선 초석 다지기
캐싱 데이터가 안보입니다.
조회를 하고 cache확인 api를 호출해도 캐시데이터가 보이지 않습니다.. 어디를 확인해봐야할까요? chatGPT를 통해 물어보고 확인해본 아래다섯가지는 정상적입니다. 코드도 깃허브 코드에서 수정한것이 없어서 질문남깁니다spring.cache.type=ehcache 설정 확인 ehcache.xml 파일 존재 여부 및 경로 확인 Ehcache 의존성 (ehcache 라이브러리) 추가 여부 확인@EnableCaching 설정 확인 @Cacheable이 정상적으로 적용되었는지 확인
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
query DSL 대신 Native Query를 사용하시는 이유가 있으신가요?
학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.안녕하세요 강의 잘 듣고 있는 학생입니다.다름이 아니라, 강의에서 query DSL 대신 Native Query를 사용하시는 이유가 있으신가요? 현업에서는 query DSL 대신 Native Query를 더 많이 사용하는걸까요??
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
velog., github 업로드 관련 문의
강사님 잘 듣고 있습니다..! 혹시 다름이 아니라 출처를 남기고 velog, github에 공부한 내용을 업로드 해도 될까요?
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
컴파일러 경고 메세지 관련 질문입니다.
안녕하세요 쿠케님 강의 잘 듣고있습니다!제네릭을 사용할 때마다 생기는 고민이 있습니다.. 제네릭을 사용하다보면 컴파일러 경고가 발생하는 경우가 있는데(IDE의 노란 밑줄) 이런 경고들을 모두 해결해서 제거해야할 까요?? 아니면 어느정도 무시를 해도 되는 걸까요??이펙티브 자바에는 런타임에 캐스팅 관련 예외가 발생할 수 있으니 이런 경고를 최선을 다해 제거하라. 라는 내용이 있기도 하고, 노란 밑줄이 보이면 눈에 거슬리기도 해서 처음에는 어느정도 제거하면서 개발을 했었는데, 시간이 지날 수록 이게 맞나 싶기도하고 책에 있는 내용이 절대적인 것도 아니기도해서 그리고 강의에서도 어느정도 무시를 하는 것 같아 질문 남깁니다!팀에서 결정한 컨벤션으로 개발하면 될 것 같은데, 다른 팀에서는 어떻게 진행하는지 궁금합니다! 최대한 제거하는지.. 어느정도는 무시(@SuppressWarnings)해도 되는지
-
미해결비전공자도 이해할 수 있는 DB 설계 입문/실전
관리자 테이블
안녕하세요.관리자 테이블에 이메일이 유저의 이메일과 다르지 않다고 생각이 들어서 합치는 게 낫다고 생각이 들었습니다. 처음에는 유저에 그냥 합쳐서 새로운 컬럼 role로 관리하려고 했는데, 중복이 있어서관리자 테이블엔, role 컬럼을 넣어서 user, admin 2개로 추가해서 이제 유저 테이블에서 FK로 사용하려고 하는데 이 방법은 어떨까요? (강의 너무 잘 듣고 있습니다, 제 멘토이십니다ㅎㅎ)
-
해결됨비전공자도 이해할 수 있는 DB 설계 입문/실전
외래 키 지정은 필수가 아닌 건가요?
안녕하세요. 강의 끝까지 다 들었는데 갑자기 외래 키 부분에서 궁금한 점이 생겼습니다. DB 설계할 때 테이블끼리 관계를 맺기 위해 외래 키를 지정하잖아요?그런데 외래 키로 지정을 안 하는 경우도 있나요? 조인 등에 사용될 속성은 있지만, 외래 키 지정은 안 해서 외래 키 제약 조건이 없도록 하는 경우도 있나요?
-
미해결Real MySQL 시즌 1 - Part 1
GAP 락에 대한 질문 드립니닷..!
해당 강의와 직접적으로 연관있지는 않으나, 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을 사용하지 않으신건가요? 감사합니다.