19,800원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Optimistic Lock 버전 관리 질문
버전 관리를 통하여 동시성을 피할수 있다고 하셨는데, 서로 다른 서버에서 동시에 version = 1 인 데이터룰 얻고, 동시에 stock = stock + 1, version = 2 로 업데이트 치면 optimistic lock 또한 동시성이 발생할 수 있는 것 아닌가요? 강의에서는 version = 1 인 데이터를 동시에 얻지만 업데이트는 순차적으로 하는 표만 보여주셔서 헷갈려요. 순차적으로 할 수 밖에 없는 메커니즘이라면 그 부분을 설명해주셨으면 좋겠습니다.
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
낙관적 락 무한루프 질문
강사님의 강의를 보며 현재 제 프로젝트에 낙관적 락을 적용시켜 보았습니다.구현하고자 하는 서비스는 쿠폰 발급 서비스이며, DB 구조는user 1 : N user_coupon N : 1 coupon 입니다. @Transactional public void issueCoupon(CouponIssueParam param, User user) { // 쿠폰 조회 Coupon coupon = getCoupon(param.getCouponId()); // 쿠폰 발급 UserCoupon userCoupon = UserCoupon.CreateUserCoupon(coupon, user); userCouponQueryService.saveUserCoupon(userCoupon); }public static UserCoupon CreateUserCoupon(Coupon coupon, User user) { // 쿠폰 검증 coupon.validateCoupon(); // 재고 감소 coupon.decreaseQuantity(); return UserCoupon.builder() .coupon(coupon) .user(user) .build(); }위 코드와 같이 유저가 특정 쿠폰 Id 를 통해 쿠폰 발급 요청을 하고, 중간 테이블에 관계가 매핑됨으로써 쿠폰 발급이 이루어집니다. public void validateCoupon() { if(this.stockStatus.equals(StockStatus.OUT_OF_STOCK)) throw new IllegalArgumentException("쿠폰이 매진되었습니다."); if(this.expiredAt.isBefore(LocalDateTime.now())) throw new IllegalArgumentException("쿠폰이 만료되었습니다."); }이때 user_coupon 생성 전 쿠폰의 매진 및 만료 상태에 따라 예외를 던지는 검증 메서드가 존재합니다. public void decreaseQuantity() { this.remainQuantity = this.remainQuantity - 1; if(this.remainQuantity <= 0){ this.stockStatus = StockStatus.OUT_OF_STOCK; } }추가로 쿠폰 내부에 재고가 감소하는 메서드가 존재하며 0에 다다를 경우 상태값을 변경해줍니다. 이때 동시성 이슈가 발생하는 이유는 user_coupon 을 insert 하면서, 부모 테이블인 coupon 의 재고를 update 하는 과정에서 발생하는 것으로 파악했습니다. 이에 쿠폰 발급 초기에 coupon 을 조회할 때 @Lock(LockModeType.OPTIMISTIC) @Query("select c from Coupon c where c.id = :couponId and c.isDeleted = false") Optional<Coupon> findOneCouponByCouponId(@Param("couponId") Long couponId);이처럼 낙관적 락 어노테이션을 달아주었으며 @Component @RequiredArgsConstructor public class OptimisticLockFacade { private final CouponService couponService; public void issueCoupon(CouponIssueParam param, User user) throws InterruptedException { while (true) { try { couponService.issueCoupon(param, user); break; } catch (Exception e) { Thread.sleep(50); // 재시도 전 잠시 대기 } } } }위와 같이 퍼사드 클래스를 생성하였습니다. @Test @DisplayName("쿠폰 여러 명 발급") void 쿠폰_여러_명_발급() throws InterruptedException { int threadCount = 1000; ExecutorService executorService = Executors.newFixedThreadPool(32); CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadNumber = i + 1; int key = i; executorService.submit(() -> { try { optimisticLockFacade.issueCoupon(param, users.get(key)); System.out.println("Thread " + threadNumber + " - 성공"); } catch (PessimisticLockingFailureException e) { System.out.println("Thread " + threadNumber + " - 락 충돌 감지"); } catch (Exception e) { System.out.println("Thread " + threadNumber + " - " + e.getMessage()); } finally { latch.countDown(); } }); } latch.await(); executorService.shutdown(); Long count = userCouponRepository.countByCouponId(param.getCouponId()); assertThat(count).isEqualTo(100); } 쿠폰의 재고가 100개며, 1,000 명의 유저가 32개의 스레드 환경에서 쿠폰 발급을 요청할 때 예상되는 발급 쿠폰 수는 100개가 되어야 합니다. 이렇게 낙관적 락을 적용하여 테스트를 수행하니 무한 루프에 빠지게 되었습니다.무한루프로 인해 테스트가 종료되지 않자, 강제적으로 정지 시킨 후 DB 를 확인했는데 쿠폰의 수는 예상한 대로 100개가 생성된 것을 확인할 수 있었습니다. 그런데 왜 메서드가 종료되지 않고 무한루프가 돌아간 것인지 이유를 모르겠습니다. public void issueCoupon(CouponIssueParam param, User user) throws InterruptedException { while (true) { try { couponService.issueCoupon(param, user); break; } catch (IllegalArgumentException e) { System.out.println("쿠폰 발급 실패: " + e.getMessage()); break; } catch (Exception e) { Thread.sleep(50); // 재시도 전 잠시 대기 } } } 혹시나 하여, 쿠폰 발급 로직에 쿠폰이 매진이 될 경우 예외를 던지는 검증 메서드가 존재했고 이에 퍼사드 클래스에서 해당 예외를 잡아내면 루프를 빠져나오게 설정했습니다. 이렇게 구현하니 테스트는 성공적으로 통과하였습니다. 현재 제가 구현한 서비스 구성에서는 이런식으로 접근하는 게 맞는 걸까요 ?
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
중간테이블에 대한 낙관적 락 적용법
현재 Member 테이블과 Appointment 테이블이 존재하는데, N:N 관계이기 때문에 아래와 같이 AppointmentUser라는 중간 테이블이 존재합니다.@Entity @Table(name = "appointment_and_user") public class AppointmentUser extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "apppointment_id", nullable = false) private Appointment appointment; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) private Member member; @Enumerated(value = EnumType.STRING) @Column(name = "member_authority", nullable = false) private MemberAuthority memberAuthority; @Version private Long version;하나의 AppointmentUser 테이블은 약속과 멤버의 id를 하나씩 가집니다. 여기서 레포지토리의 코드는 이러합니다.public interface AppointmentUserRepository extends JpaRepository<AppointmentUser, Long> { ... @Lock(LockModeType.OPTIMISTIC) @Query("select au from AppointmentUser au where au.id = :id") AppointmentUser findByIdWithOptimisticLock(Long id);findByIdWithOptimistic 메서드를 통하여 특정 유저-약속 테이블의 데이터를 통해 AppointmentUser 객체를 반환합니다. 서비스 계층의 코드는 아래와 같습니다.@Transactional public void updateAuthority(Long appointmentId, Long loginMemberId, Long targetMemberId) { Member loginMember = memberRepository.getById(loginMemberId); Member targetMember = memberRepository.getById(targetMemberId); Appointment appointment = appointmentRepository.getById(appointmentId); AppointmentUser loginAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(loginMember, appointment); AppointmentUser targetAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(targetMember, appointment); appointment.changeTitle("asd"); validateIsAdminMember(loginAppointmentUser.getId()); MemberAuthority targetAuthority = targetAppointmentUser.getMemberAuthority(); targetAppointmentUser.updateAuthority(MemberAuthority.getAnotherAuthority(targetAuthority)); appointmentRepository.save(appointment); appointmentUserRepository.save(targetAppointmentUser); } private void validateIsAdminMember(Long loginAppointmentUserId) { if (appointmentRepository.findByIdWithOptimisticLock(loginAppointmentUserId).getMemberAuthority() != MemberAuthority.ADMIN) { throw new NotAdminMemberException(); } } <로직 설명>하나의 약속 내에 멤버 두명이 존재각 멤버들은 ADMIN or NORMAL 권한을 갖고 있음.두명 다 약속 내 에서 ADMIN 권한을 갖고 있다는 상황을 가정. (ADMIN은 AppointmentUser 엔티티의 MemberAuthority 라는 필드의 Enum 값입니다.)두명이 서로를 동시에 ADMIN에서 NORMAL로 권한을 박탈하는 경우, 하나의 약속 안에 ADMIN인 사람이 없어지는 예외적인 문제 상황이 발생함.그래서 @Version을 AppointmentUser 엔티티의 필드로 등록하여 해결하려고 했으나..사용자 A와 B가 있다고 할 때 service 코드 내의 validateIsAdminMember 메서드를 통해 상대방의 권한을 박탈하려는 유저(본인)가 ADMIN인지 검증하여 ADMIN이 맞다면 박탈하고, ADMIN이 아니라면, 예외를 던지게 끔 하는 로직에서,레포지토리 내의 findByIdWithOptimisticLock 메서드를 동시에 접근했을때 Version 필드를 통해 동시성 문제를 제어할 수 있다고 생각했으나..validateIsAdminMember로 검증하는 A와 B의 AppointmentUser 엔티티는 서로 다른 객체(데이터)이기 때문에 서로 다른 테이블의 Version 값을 변경하기 때문에 동시성 보장이 안됨..그래서 Appointment에 Version필드를 넣어주려 했지만, Version값은 해당 테이블에 변화가 생겨야 변한다.하지만 로직상, AppointmentUser(중간테이블)에 변화가 생기는게 맞다...위와 같은 중간 테이블 사용으로 인한 문제가 발생하였을 때 어떻게 강의자님이시라면 어떻게 해결하실지 궁금합니다!
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
version 컬럼이 증가하지 않는 이유
현재 강의 내용을 바탕으로 OptimisticLock 을 구현중인데,version이 증가하지 않는 이유를 알고 싶습니다.도메인은 이렇습니다.@Entity @Table(name = "appointment") public class Appointment extends BaseEntity { ... @OneToMany(mappedBy = "appointment") private List<AppointmentUser> appointmentUsers = new ArrayList<AppointmentUser>(); @Version private Long version; 그리고, 레포지토리는 이렇습니다.public interface AppointmentRepository extends JpaRepository<Appointment, Long> { ... @Lock(LockModeType.OPTIMISTIC) @Query("SELECT au FROM Appointment a " + "JOIN a.appointmentUsers au " + "WHERE au.id = :appointmentUserId ") AppointmentUser findByIdWithOptimisticLock(Long appointmentUserId); } 그리고, 서비스 계층 메서드는 이렇습니다.@Transactional(readOnly = true) @Service public class AppointmentUserService { ... @Transactional public void updateAuthority(Long appointmentId, Long loginMemberId, Long targetMemberId) { Member loginMember = memberRepository.getById(loginMemberId); Member targetMember = memberRepository.getById(targetMemberId); Appointment appointment = appointmentRepository.getById(appointmentId); AppointmentUser loginAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(loginMember, appointment); AppointmentUser targetAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(targetMember, appointment); validateIsAdminMember(loginAppointmentUser.getId()); MemberAuthority targetAuthority = targetAppointmentUser.getMemberAuthority(); targetAppointmentUser.updateAuthority(MemberAuthority.getAnotherAuthority(targetAuthority)); appointmentUserRepository.save(targetAppointmentUser); } private void validateIsAdminMember(Long loginAppointmentUserId) { if (appointmentRepository.findByIdWithOptimisticLock(loginAppointmentUserId).getMemberAuthority() != MemberAuthority.ADMIN) { throw new NotAdminMemberException(); } } }위 updateAuthority 메서드 내에서 validateIsAdminMember를 호출하여, validateIsAdminMember 안에 있는 findByIdWithOptimisticLock을 통하여 Version을 올려주어서 낙관적 락을 성공적으로 구현할 수 있을 줄 알았는데 테스트를 해보니 아래와 같이 실패합니다.. @Test void 동시성_테스트() throws InterruptedException { Logger logger = Logger.getLogger(MultiThreadTest.class.getName()); ExecutorService executorService = Executors.newFixedThreadPool(2); CountDownLatch latch = new CountDownLatch(2); AtomicReference<Boolean> flag = new AtomicReference<>(false); executorService.execute(() -> { try { logger.log(Level.INFO, "첫 번째 요청 시작: member1 -> member2"); logger.log(Level.INFO, "버전1전" + appointmentRepository.getById(1L).getVersion()); appointmentUserService.updateAuthority(1L, 1L, 2L); logger.log(Level.INFO, "버전1완" + appointmentRepository.getById(1L).getVersion()); logger.log(Level.INFO, "첫 번째 요청 완료: member1 -> member2"); } catch (Exception e) { logger.log(Level.SEVERE, "첫 번째 요청 중 예외 발생", e); flag.set(true); } finally { latch.countDown(); } }); executorService.execute(() -> { try { logger.log(Level.INFO, "두 번째 요청 시작: member2 -> member1"); logger.log(Level.INFO, "버전2전" + appointmentRepository.getById(1L).getVersion()); appointmentUserService.updateAuthority(1L, 2L, 1L); logger.log(Level.INFO, "버전2완" + appointmentRepository.getById(1L).getVersion()); logger.log(Level.INFO, "두 번째 요청 완료: member2 -> member1"); } catch (Exception e) { logger.log(Level.SEVERE, "두 번째 요청 중 예외 발생", e); flag.set(true); } finally { latch.countDown(); } }); latch.await(); };위와 같이, 두 개의 스레드에서 진행을 하였고, 각 스레드 내에서 updateAuthority()를 실행 전후에 version을 찍어보았으나, 찍힌 4개의 version 모두 0이 나왔고 또한, 동시성 제어도 안되는 상황입니다.왜 0이 나오는걸까요.. 궁금합니다!(컴파일 에러는 없습니다)
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
PessimisticLock 관련
안녕하세요. 강의 잘 듣고 있습니다. StockRepository 인터페이스에 PESSIMISTIC_WRITE 락을 걸고 테스트를 돌리면 잘 통과합니다. 다만, 궁금한 것은 쿼리를 보고 나서인데요,예를 들어, thread-5가 SELECT 쿼리를 젤 먼저 날립니다. 그러면 thread-5가 락을 가지고 있는거겠죠? 이후 다른 스레드들도 모두 SELECT 쿼리만 날리게 됩니다. 제가 예상한 것은 thread-5가 UPDATE 쿼리를 날리고 메서드가 종료되면 COMMIT 되면서 락을 놓게 되고, 다른 스레드가 락을 가져가면서 또 UPDATE 쿼리를 날릴 수 있게 되는 것이였습니다. 하지만 제일 먼서 UPDATE 쿼리를 날린 것은 thread-5가 아니라 다른 스레드였는데요, 혹시 왜 그런 것인지 설명 가능하실까요?
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
docker restart시에 java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
2024-04-22 21:51:48.515 ERROR 1928 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization. java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YESTerminal상에서 아래와 같이 Docker를 재시동해서Docker restart 8550a9a141fdDocker ps로 아래와 같이 제대로 동작하는 것도 확인했고igwangmin@igwangmin-ui-MacBookPro ~ % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8550a9a141fd mysql "docker-entrypoint.s…" 2 days ago Up 17 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp mysql컨테이너가 잘 작동하는것도 아래처럼 보았습니다.다른 게시물에서 알려주신 방법으로 Terminal을 확인했을때 아래와 같이 로그인을 하고 databases를 정상적으로 사용했지만 서버를 가동시켰을 때 하루 전에는 잘 작동했지만 다시 시작을 해보니 아래와 같이 오류가 발생합니다. 아래의 오류 구문이 발생합니다. 혹시 다른 방법이 있을까요?java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
- 해결됨재고시스템으로 알아보는 동시성이슈 해결방법
네임드락 테스트 오류
저는 @Query 어노테이션의 값을 아래와 같이 강의 내용대로 따라했을 때 오류가 발생해서 public interface LockRepository extends JpaRepository<Stock, Long> { @Query(value = "select get_lock(:key, 3000)", nativeQuery = true) void getLock(String key); @Query(value = "select release_lock(:key)", nativeQuery = true) void releaseLock(String key); }아래와 같이 정합성이 맞지 않아서 테스트를 통과하지 못했습니다.그래서 구문을 아래와 같이 바꿔서 통과했는데 참고 하시길 바랍니다. ?1의 값은 key로의 값으로 고정되어 있는데 이럴 경우 문제가 없을지 궁금합니다.@Query(value = "select get_lock(?1, 3000)", nativeQuery = true) void getLock(String key); @Query(value = "select release_lock(?1)", nativeQuery = true) void releaseLock(String key);
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Docker restart시 spring boot 연동이 안됩니다.
igwangmin@igwangmin-ui-MacBookPro ~ % docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8550a9a141fd mysql "docker-entrypoint.s…" 15 hours ago Exited (0) 11 hours ago mysql igwangmin@igwangmin-ui-MacBookPro ~ % docker container restart 8550a9a141fdaee67b6f7c586a24085c98a9b99512cb7a8901f0c23cf9082536 8550a9a141fdaee67b6f7c586a24085c98a9b99512cb7a8901f0c23cf9082536 igwangmin@igwangmin-ui-MacBookPro ~ % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8550a9a141fd mysql "docker-entrypoint.s…" 15 hours ago Up 4 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp mysqlDocker restart를 한 후 Spring을 가동했을 때 아래와 같은 오류가 발생합니다. 2024-04-21 09:25:02.485 INFO 1378 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-04-21 09:25:03.641 ERROR 1378 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization. java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.29.jar:8.0.29] at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.29.jar:8.0.29] at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:828) ~[mysql-connector-java-8.0.29.jar:8.0.29] at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:448) ~[mysql-connector-java-8.0.29.jar:8.0.29] at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) ~[mysql-connector-java-8.0.29.jar:8.0.29] 기존에 알려주신 방법인 아래와 같은 터미널 창에서 입력후 재실행하였으나 똑같은 오류가 발생해 혹시 다른 방법이 존재할까요igwangmin@igwangmin-ui-MacBookPro Mikor % docker exec -it mysql bash bash-4.4# mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.3.0 MySQL Community Server - GPL Copyright (c) 2000, 2024, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use stock_example; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | stock_example | | sys | +--------------------+ 5 rows in set (0.00 sec) mysql> use stock_example; Database changed mysql> Spring 버전은 아래와 같고 plugins { id 'org.springframework.boot' version '2.7.1' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.shop' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { compileOnly { extendsFrom annotationProcessor } } 도커 버전은 아래와 같습니다. igwangmin@igwangmin-ui-MacBookPro ~ % docker version Client: Docker Engine - Community Version: 26.0.0 API version: 1.44 (downgraded from 1.45) Go version: go1.22.1 Git commit: 2ae903e86c Built: Wed Mar 20 15:10:03 2024 OS/Arch: darwin/arm64 Context: desktop-linux Server: Docker Desktop 4.28.0 (139021) Engine: Version: 25.0.3 API version: 1.44 (minimum version 1.24) Go version: go1.21.6 Git commit: f417435 Built: Tue Feb 6 21:14:22 2024 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.6.28 GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb runc: Version: 1.1.12 GitCommit: v1.1.12-0-g51d5e94 docker-init: Version: 0.19.0 GitCommit: de40ad0
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Docker의 mysql과 스프링 연동이 안됩니다 (윈도우)
docker pull mysql:8.3.0 $ docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=1234 -d -p 3305:3305 mysql:8.3.0 ... use stock_example로컬에 mysql이 이미 깔려있어서 포트번호를 3305로 바꿔서 만들었어요. 위와같이 도커 초기설정 후 만든 yml 파일입니다spring: jpa: hibernate: ddl-auto: create show-sql: true datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3305/stock_example username: root password: 1234 logging: level: org: hibernate: SQL: DEBUG type: descriptor: sql: BasicBinder: TRACE 이렇게하고 Application을 실행했는데 com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failureThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:815) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:438) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:189) ~[mysql-connector-j-8.3.0.jar:8.3.0] at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:359) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:201) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:470) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:100) ~[HikariCP-5.0.1.jar:na] at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112) ~[HikariCP-5.0.1.jar:na] at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:428) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:61) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:276) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:107) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:68) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.boot.model.relational.Database.<init>(Database.java:45) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:223) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:191) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:170) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1432) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1503) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.1.5.jar:6.1.5] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) ~[spring-orm-6.1.5.jar:6.1.5] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.1.5.jar:6.1.5] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.1.5.jar:6.1.5] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366) ~[spring-orm-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1833) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.5.jar:6.1.5] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1234) ~[spring-context-6.1.5.jar:6.1.5] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:952) ~[spring-context-6.1.5.jar:6.1.5] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.5.jar:6.1.5] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.4.jar:3.2.4] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.4.jar:3.2.4] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.4.jar:3.2.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.4.jar:3.2.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.4.jar:3.2.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.4.jar:3.2.4] at com.example.stock.StockApplication.main(StockApplication.java:10) ~[main/:na]Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failureThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. 이런 오류가 나네요 ㅠㅠ 보면 연결이 안되는거 같은데 어디서 문제일까요..
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Lock 해제 시점이 궁금합니다.
안녕하세요!Lock을 해제하는 시점이 궁금합니다.아래 단계에서 어느 시점 이전 혹은 이후 인지 알려주시면 감사드리겠습니다. 1) 트랜잭션 시작2) Lock 획득 후 비즈니스 로직 실행3) 트랜잭션 커밋 (혹시, 3번 이전에 Lock을 해제할 경우 이전과 동일한 동시성 문제가 발생할 것 같은데.. 우선 답변부탁드립니다!)
- 해결됨재고시스템으로 알아보는 동시성이슈 해결방법
프로젝트를 처음 시작시에 java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 오류가 발생합니다.
spring: jpa: hibernate: ddl-auto: create show-sql: true datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/stock_example username: root password: 1234 # JPA 쿼리가 어떻게 나가는지 logging: level: org: hibernate: SQL: DEBUG type: descriptor: sql: BasicBinder: TRACEyml 설정은 위와 같이 하고 docker 이미지 확인시에 아래와 같이 작동하고 있습니다. igwangmin@igwangmin-ui-MacBookPro ~ % docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03d3fae9019b mysql "docker-entrypoint.s…" 4 days ago Up 16 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp mysql DB 조회 시에도 아래와 같이 나오는 데 혹시 제가 놓친 부분이 있을까요? mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | stock_example | | sys | +--------------------+ 5 rows in set (0.01 sec)
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Redisson을 Ebedded로 실행하는 방법이 있을까요?
안녕하세요.Redisson을 이용해서 분산락을 적용하고 있습니다.로컬과 테스트 코드를 실행할 때, Redis를 로컬에서 띄우지 않으면 사용할 수가 없습니다. 그래서 implementation 'com.github.codemonstur:embedded-redis:1.4.2' 의존성을 추가해서 Redis를 embedded로 실행할 수 있도록 했습니다. 하지만 Redisson은 연결이 되지 않는것처럼 보여서요. 혹시 방법이 있을까요? 만약, embedded로 실행할 수 없다면 로컬, 테스트에서는 어떤 방식으로 진행하시는지 경험적 의견을 여쭤보고 싶습니다. 감사합니다. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisson' defined in class path resource [org/redisson/spring/starter/RedissonAutoConfigurationV2.class]: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception with message: java.util.concurrent.ExecutionException: org.redisson.client.RedisConnectionException: Unable to connect to Redis server: localhost/127.0.0.1:6379
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
테스트가 실패해요
하다가 안돼서 깃허브 소스 복붙하고 돌려도 테스트가 실패해요.
- 해결됨재고시스템으로 알아보는 동시성이슈 해결방법
docker ps시에 실행되는 이미지가 없다고 나옵니다.
docker ps시에 실행되는 이미지가 아래와 같이 존재하지 않습니다. 혹시 docker를 homebrew를 이용해 설치 한 후 desktop버전도 사이트에서 직접 깔아서 문제가 되는걸까여?
- 해결됨재고시스템으로 알아보는 동시성이슈 해결방법
docker version 오류
homebrew로 docker 설치하는 과정에서 docker version 확인 시에 아래와 같이 오류가 발생합니다. igwangmin@igwangmin-ui-MacBookPro ~ % brew list ==> Formulaeca-certificates libevent protobuf@21docker libfido2 xzdocker-completion lz4 zlibicu4c mysql zstdlibcbor openssl@3 ==> Casksdbeaver-community postmanigwangmin@igwangmin-ui-MacBookPro ~ % brew link docker Warning: Already linked: /opt/homebrew/Cellar/docker/26.0.0To relink, run: brew unlink docker && brew link dockerigwangmin@igwangmin-ui-MacBookPro ~ % docker versionClient: Docker Engine - CommunityVersion: 26.0.0API version: 1.45Go version: go1.22.1Git commit: 2ae903e86cBuilt: Wed Mar 20 15:10:03 2024OS/Arch: darwin/arm64Context: defaultCannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 그래서 docker 자체를 아래와 같이 삭제하고 새로 시작했는데도 계속 같은 오류가 발생합니다.
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
테스트 수행 결과가 항상 에러가 발생합니다.
안녕하세요. 해당 강의를 듣고 있는 수강생입니다. 제가 작성한 코드에서는 계속 테스트가 실패하여 강사님이 올려주신 코드를 기반으로 테스트를 재수행하였습니다. 그런데 여전히 해당 화면처럼 테스트가 실패하여서 어떤 문제일까하여 질문드립니다.코드 : https://github.com/sangyongchoi/stock-example 이슈 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.0) 2024-03-24 19:26:47.245 INFO 5004 --- [ main] c.e.s.facade.NamedLockStockFacadeTest : Starting NamedLockStockFacadeTest using Java 17.0.10 on windows with PID 5004 (started by ac2di in C:\Users\ac2di\Desktop\vscode\stock-example-main) 2024-03-24 19:26:47.247 INFO 5004 --- [ main] c.e.s.facade.NamedLockStockFacadeTest : No active profile set, falling back to 1 default profile: "default" 2024-03-24 19:26:48.250 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode! 2024-03-24 19:26:48.252 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-03-24 19:26:48.519 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 257 ms. Found 2 JPA repository interfaces. 2024-03-24 19:26:48.532 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode! 2024-03-24 19:26:48.535 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. 2024-03-24 19:26:48.553 INFO 5004 --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data Redis - Could not safely identify store assignment for repository candidate interface com.example.stock.repository.LockRepository. If you want this repository to be a Redis repository, consider annotating your entities with one of these annotations: org.springframework.data.redis.core.RedisHash (preferred), or consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.KeyValueRepository. 2024-03-24 19:26:48.554 INFO 5004 --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data Redis - Could not safely identify store assignment for repository candidate interface com.example.stock.repository.StockRepository. If you want this repository to be a Redis repository, consider annotating your entities with one of these annotations: org.springframework.data.redis.core.RedisHash (preferred), or consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.KeyValueRepository. 2024-03-24 19:26:48.554 INFO 5004 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8 ms. Found 0 Redis repository interfaces. 2024-03-24 19:26:49.201 INFO 5004 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-03-24 19:26:49.275 INFO 5004 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.9.Final 2024-03-24 19:26:49.465 INFO 5004 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final} 2024-03-24 19:26:49.622 INFO 5004 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-03-24 19:26:49.962 INFO 5004 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-03-24 19:26:49.986 INFO 5004 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect 2024-03-24 19:26:50.663 DEBUG 5004 --- [ main] org.hibernate.SQL : drop table if exists stock Hibernate: drop table if exists stock 2024-03-24 19:26:50.708 DEBUG 5004 --- [ main] org.hibernate.SQL : create table stock (id bigint not null auto_increment, product_id bigint, quantity bigint, version bigint, primary key (id)) engine=InnoDB Hibernate: create table stock (id bigint not null auto_increment, product_id bigint, quantity bigint, version bigint, primary key (id)) engine=InnoDB 2024-03-24 19:26:50.762 INFO 5004 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2024-03-24 19:26:50.775 INFO 5004 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-03-24 19:26:51.083 INFO 5004 --- [ main] org.redisson.Version : Redisson 3.17.4 2024-03-24 19:26:51.313 INFO 5004 --- [isson-netty-2-6] o.r.c.pool.MasterPubSubConnectionPool : 1 connections initialized for localhost/127.0.0.1:6379 2024-03-24 19:26:51.468 INFO 5004 --- [sson-netty-2-20] o.r.c.pool.MasterConnectionPool : 24 connections initialized for localhost/127.0.0.1:6379 2024-03-24 19:26:52.949 WARN 5004 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-03-24 19:26:54.634 INFO 5004 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' 2024-03-24 19:26:54.714 INFO 5004 --- [ main] c.e.s.facade.NamedLockStockFacadeTest : Started NamedLockStockFacadeTest in 7.788 seconds (JVM running for 9.06) 2024-03-24 19:26:55.040 DEBUG 5004 --- [ main] org.hibernate.SQL : insert into stock (product_id, quantity, version) values (?, ?, ?) Hibernate: insert into stock (product_id, quantity, version) values (?, ?, ?) 2024-03-24 19:26:55.051 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1] 2024-03-24 19:26:55.052 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [100] 2024-03-24 19:26:55.052 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [0] 2024-03-24 19:26:55.251 DEBUG 5004 --- [ main] org.hibernate.SQL : select stock0_.id as id1_0_0_, stock0_.product_id as product_2_0_0_, stock0_.quantity as quantity3_0_0_, stock0_.version as version4_0_0_ from stock stock0_ where stock0_.id=? Hibernate: select stock0_.id as id1_0_0_, stock0_.product_id as product_2_0_0_, stock0_.quantity as quantity3_0_0_, stock0_.version as version4_0_0_ from stock stock0_ where stock0_.id=? 2024-03-24 19:26:55.253 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1] 2024-03-24 19:26:55.316 DEBUG 5004 --- [ main] org.hibernate.SQL : select stock0_.id as id1_0_, stock0_.product_id as product_2_0_, stock0_.quantity as quantity3_0_, stock0_.version as version4_0_ from stock stock0_ Hibernate: select stock0_.id as id1_0_, stock0_.product_id as product_2_0_, stock0_.quantity as quantity3_0_, stock0_.version as version4_0_ from stock stock0_ 2024-03-24 19:26:55.334 DEBUG 5004 --- [ main] org.hibernate.SQL : delete from stock where id=? and version=? Hibernate: delete from stock where id=? and version=? 2024-03-24 19:26:55.335 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1] 2024-03-24 19:26:55.335 TRACE 5004 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [0] 2024-03-24 19:26:55.437 INFO 5004 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-03-24 19:26:55.441 INFO 5004 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-03-24 19:26:55.514 INFO 5004 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.테스트 수행 시 콘솔 출력 창도 같이 첨부드립니다. 감사합니다.
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
yml 설정 내용 공유
혹시 귀찮으신분들 있으실까봐 공유드립니다.2칸(뎁스) 는 주의부탁드려요 spring: jpa: hibernate: ddl-auto: create show-sql: true datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/stock_example username: root password: 1234 # JPA 쿼리가 어떻게 나가는지 logging: level: org: hibernate: SQL: DEBUG type: descriptor: sql: BasicBinder: TRACE
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
Pessmistic Lock 획득 순서가 보장되는지 궁금합니다
안녕하세요 강사님.다름이 아니라 Pessmistic Lock 획득을 요청한 쓰레드 순서가 쓰레드1, 쓰레드2 쓰레드3이고 이때 쓰레드1이 먼저 락을 획득한 후 락을 해제하면,먼저 요청한 쓰레드2가 락을 반환한받는지 궁금합니다.제가 gpt 및 postgresql, spring data jpa 공식문서를 검색했을 땐, 락 획득 요청대로 락 획득 순서가 보장된다는 내용은 없었어서,혹시 이부분에 대해 알고계신지 궁금하여 질문 올립니다.
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
네임드락 선점 시간 설정
안녕하세요! 네임드락 사용 시 락 선점 시간 설정에 있어 궁금한 부분이 생겨 질문 드립니다. Named Lock 활용해보기 강의 25초 부분에 "선점 시간이 끝나야 락이 해제된다" 고 언급해주셨는데, 이 선점 시간을 어떻게 설정하는지 궁금합니다. "get_lock" 명령어에 주는 파라미터는 해당 lock 을 획득하기 위해 대기하는 시간의 최대값으로 알고 있습니다. 그래서 hikariCp 에서 얻어온 커넥션을 점유할 수 있는 최대 시간 설정 값도 찾아봤는데 찾지 못했습니다.별도의 타이머를 구현해서 타이머가 끝나면 해제로직을 실행시켜야 되는걸까요??혹은, @Transactional 의 timeout 설정 값을 통해 선점 시간을 설정하는 방법도 가능할 것 같습니다.
- 미해결재고시스템으로 알아보는 동시성이슈 해결방법
분산 DB 에서 비관적 락을 통한 동시성 제어
안녕하세요. 분산락을 언제 쓰는게 좋을지 고민하다가 몇 가지 궁금증이 생겨 질문드립니다.분산 DB 환경이 무엇을 의미하는지?흔히 분산 DB 환경에서 낙관적, 비관적 락으로 동시성 문제를 해결하기 힘들다고 얘기하더라고요. 여기서 말하는 분산 DB 라는 것이 샤딩에 의해 여러 DB 서버가 있는 것인지, 동일한 데이터를 저장하는 DB 서버가 여러 대 있는 환경을 의미하는 것인지 모르겠습니다.예를 들어, Ticket 이란 데이터를 저장하는데 동일한 ticket 데이터가 DB server 1, DB server 2 에 저장되어 있는 환경일까요??만약 분산 DB 가 샤딩인 경우 비관적 락으로도 동시성 이슈를 해결할 수 있을 것 같은데 맞을까요?아래 그림처럼 ticketId = 1 인 티켓을 예매하기 위한 요청이 동시에 올 경우 입니다. 처음 x-lock 을 잡은 요청이 끝나야 뒤늦게 온 요청이 해당 티켓의 잔여 수량을 확인하고 예매 하기 때문에 샤딩으로 인한 분산 DB 에서는 비관적 락으로 동시성 이슈를 해결할 수 있을 것 같습니다.동일한 Ticket 데이터가 여러 DB 서버에 중복되어 저장된 분산 DB 환경에서는 분산락을 사용해야 될 것 같습니다.그러나, 샤딩은 동일한 Ticket 데이터에 접근하기 위해서는 동일한 서버로 접근하기 때문에 비관적 락으로도 충분히 해결 가능할 것 같아서 질문 드립니다!