묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
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
-
미해결파이썬 동시성 프로그래밍 : 데이터 수집부터 웹 개발까지 (feat. FastAPI, async, await)
proxy 사용 질문
안녕하세요.사내에서 강의를 듣다보니 proxy를 타도록 되어 있습니다.basic-fetcher에서는 아래 두가지 방식이 모두 동작하는데요.os.environ["HTTP_PROXY"] = "http://xxx..." os.environ["HTTPS_PROXY"] = "http://xxx..." os.environ["PYTHONHTTPSVERIFY"] = "0"def fetcher(session, url): with session.get(url, proxies=proxies, verify=False) as respose: return respose.textcoroutine-fetcher 에서는 ClientSession()에서 두가지 모두 오류가 납니다.1번async def fetcher(session, url): async with session.get(url, proxies=proxies, verify=False) as respose: return await respose.text() async def main(): urls = ["https://naver.com", "https://google.com", "https://instagram.com"] async with aiohttp.ClientSession() as session: result = await fetcher(session, urls[0]) print(result)2번async def fetcher(session, url): async with session.get(url, verify=False) as respose: return await respose.text() async def main(): urls = ["https://naver.com", "https://google.com", "https://instagram.com"] async with aiohttp.ClientSession() as session: result = await fetcher(session, urls[0]) print(result) 해결방법과 함께 proxy 환경에서는 프록시 정보를 어떤 구조로 가지고 있어야 효율적일지 문의드립니다.
-
미해결성공적인 진짜 iOS 개발자 되기 [기초부터 실무까지]
테이블 뷰 데이터 업데이트 관련 질문
안녕하세요 강사님! 영상 잘 보고 공부하고 있습니다!영상에서 combine을 사용하여 데이터 바인딩 하셨는데 제가 이해하기로는 @Published가 붙은 데이터가 업데이트 되면 그것을 구독하는 userName3, userAge3 메서드가 호출되고 그래서 테이블 뷰 안에 데이터가 변경되는 것으로 알고있는데 reloadData를 호출하지 않으면 변경이 이루어지지 않습니다. reloadData를 호출하여 테이블 뷰의 변경사항을 업데이트 한다면 굳이 각각의 셀에 데이터를 바인딩 할 필요 없이 셀에서는 배열 안에 데이터를 그냥 가져오고 viewDidLoad안에서 데이터가 변경되면 reloadData를 호출하도록 구현하는 것과 차이점이 있을까요?
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
스레드 중지 – flag variable vs interrupt() - 2 강의에서 질문
안녕하세요.처음 강의 부분에스레드 1번 2번이 있는데 1번에 sleep을 주어서 컨텍스트 스위칭이 일어나 코어 캐시 메모리에서 false라는 값을 업데이트한다고 하셨는데 sleep을 주지 안 아도 시간 자원이 만료되면 자동으로 컨텍스트 스위칭이 일어나서 둘 이 번갈아 가며 실행 될 때캐시 메모리가 비워지지 않는 건가요? 이 부분 때문에 이해가 잘 안 갑니다. 아니면 멀티 코어 환경이라 스레드1 캐시 메모리는 그대로 true고 sleep으로 인해서 초기화가 된건가요?
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
Pessmistic Lock 획득 순서가 보장되는지 궁금합니다
안녕하세요 강사님.다름이 아니라 Pessmistic Lock 획득을 요청한 쓰레드 순서가 쓰레드1, 쓰레드2 쓰레드3이고 이때 쓰레드1이 먼저 락을 획득한 후 락을 해제하면,먼저 요청한 쓰레드2가 락을 반환한받는지 궁금합니다.제가 gpt 및 postgresql, spring data jpa 공식문서를 검색했을 땐, 락 획득 요청대로 락 획득 순서가 보장된다는 내용은 없었어서,혹시 이부분에 대해 알고계신지 궁금하여 질문 올립니다.
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
현재 자바 스레드 모델
안녕하세요 강사님강의에서 현재 자바에서 채택되어 사용 중인 스레드 모델이일대일이라고 하셨는데 또 인터넷에 찾아보면 다대 다이라고 하더라구요 무엇으로 이해하면 될까요.?
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
native 자바 스레드 동작 원리 질문
안녕하세요. 강사님제가 이해하고 있는 게 맞는지 궁금하여 질문드립니다.자바 코드에서 두 개의 스레드를 생성하여 실행될 때의아래 메커니즘이 맞을까요..?↓ ↓ ↓1. thread.start를 두 번 하여 시스템 콜 호출 후 커널 영역에 커널 스레드 2개 생성 후 사용자 스레드와 맵핑-> 1 코어 하드웨어 스레드 1개가 OS 스케줄러에 의해 2개의 커널 스레드를 반복 선택하여 실행(매핑된 사용자 스레드같이 반복)(이 부분에서 자바 스레드는 기본적으로 동시성 방식으로 작동하고 작업을 병렬 방식으로 처리를 해야지 N 개의 코어를 사용하여 동시 처리되는 건가요?)--------------------------------------------------2. 커널 스레드는 사용자 스레드의 존재를 모르고 프로세스의 존재만 알고 있으며 PCB 정보를 가지고 있기 때문에 커널 스레드 TCB에 사용자 스레드 컨텍스 정보들을 저장하여 게속 스위칭을 반복하며 처리
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
네임드락 선점 시간 설정
안녕하세요! 네임드락 사용 시 락 선점 시간 설정에 있어 궁금한 부분이 생겨 질문 드립니다. 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 데이터에 접근하기 위해서는 동일한 서버로 접근하기 때문에 비관적 락으로도 충분히 해결 가능할 것 같아서 질문 드립니다!
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
PessimisticLock을 분산락으로 활용하는 질문에 대한 답변 남기겠습니다.
github repository url: https://github.com/developer-yoni/ecommerce/tree/study/concurrency/redis여기서 study/concurrency/redis 브랜치를 확인해주시면 됩니다.StockServiceTest의 400번째 라인부터 503번째 라인까지테스트 코드가 작성되어 있습니다.큰 흐름은 동시성 이슈가 발생가능한 Stock에는 Lock을 걸지 않고,다른 Entity인 Market Entity에 PessimisticLock을 걸어,PessimisticLock을 분산락으로 활용하려는 시도 입니다.여기서 4_1 테스트는 Market에 PessimisticLock을 거는 트랜잭션과 Stock의 재고를 감소시키는 트랜잭션을 하나의 트랜잭션으로 묶었고,4_2 테스트는 별개의 트랜잭션으로 분리했습니다.이때 질문은 다음과 같습니다Q1. 동시성 이슈가 일어나지 않는 다른 Entity에 PessimisticLock을걸어 분산락처럼 활용하는게 문제가 되지 않을지 궁금합니다.혹시 문제가 된다면 , 어떤 측면에서 문제가 될지 궁금합니다.왜냐하면 어차피 동시성 이슈가 일어날 수 있는 측면의 값을 커밋하여 update함과 동시에PessmisticLock을 반환하는 것이니 문제가 되지 않을것이라고 생각했기 때문입니다.Q2. PessimisticLock을 건 트랜잭션이 커밋되거나 롤백될 때 비로소 PessmisticLock이 반환된다는 점을 근거로,4_2 테스트에서는 일부로 재고감소를 먼저 커밋한 후,Market의 PessimisticLock을 커밋하여,반드시 업데이트가 이뤄난 후 락을 반환하는것을 의도하였습니다.그러나 제 의도와 다르게 4_2 테스트는 계속 lock이 걸려있는?흐름을 보이면서 테스트에 실패합니다.그원인을 잘 모르겠습니다.바쁘신 와중에 답변 달아주셔서 감사합니다 강사님.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
혹시 PessimisticLock을 NameLock처럼 분산락으로 활용할 수는 없을까요?
질문의 의도처럼 변경하고자 하는 Entity는 A인데,사실 Entity B에 PessimisticLock을 획득하고, 획득한 쓰레드들에 한해서만 A를 변경하게 하면 PessimisticLock을 분산락으로 활용할 수 있을것 같았습니다.그러나 사실, 제가 테스트해본 결과 동시성 이슈가 발생해서, 그 원인을 모르겠습니다.제 생각은 PessimisticLock을 얻지 못한 쓰레드는 계속 대기하면서 변경하고자 하는 Entity B를 변경할 수 없을것이라고 생각했습니다.@Transactionalpublic void decrease2(Long id, Long quantity) {//0. 여기서 동일한 UserEntity에 대해 PesimisticLock을 건다 userRepository.findByIdWithPessimisticLock(userId); //1. stock 조회 Stock stock = stockRepository.findByIdAndEntityStatus(id, EntityStatus.ACTIVE).orElseThrow(() -> new ApiException(ApiCode.CODE_000_0011, "재고 감소시, 요청값으로 들어온 stockId로 Stock 조회 실패")); //2. 재고 감소 // 여기서 stock.decreaseInventoryQuantity(quantity); //3. 갱신된 값을 저장 stockRepository.saveAndFlush(stock); // 마지막에 트랜잭션 커밋 되야 -> Pessimistic Lock이 반환된다}
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
강의의 NamedLockStockFacade의 decrease()에서 @Transactional을 붙인 이유 질문
안녕하세요 강사님.제가 gpt를 통해 확인한 결과는, MySQL의 getLock(), releaseLock()은 트랜잭션 상태와는 무관하게 동작할 수 있다는 것이었습니다. 또한 NamedLockStockFacade에서 호출하는 StockService.decrease()는 REQUIRES_NEW에 의해 별도의 트랜잭션으로 관리되고 커밋 됩니다.따라서 StockService.decrease()에 의해 재고가 감소한 후,NamedLockStockService에서 releaseLock()으로 락을 반환할 때 예외가 터져도, 이미 커밋된 재고감소는 롤백되지 않을 것 같습니다.그럼에도 NamedLockStockFacade에서 @Transactional을 붙이신 별도의 의도가 있는지 궁금합니다.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
OptimisticLockStockService에서 @Transactional을 붙이게 되면 무한루프에 빠지는 이유 질문
안녕하세요 강사님. 강사님 강의를 예전에 들었는데 , 현업에서 실제 동시성 처리를 하려고 다시 보니 예전에 안보이던게 보이는 것 같습니다. 감사합니다.제가 질문하고 싶은 부분은 강의를 들으면서 습관적으로 OptimisticLockStockService에서 @Transactional을 붙였는데 무한루프에 빠졌습니다. 앞의 질문에 대한 답변으로 강사님이 보내주신 답변을 힌트로 그 원인을 생각했는데 , 이부분에 대해 피드백을 주시면 감사하겠습니다.제가 이해한 바로는 OptimisticLockStockFacade에 @Transactional을 붙이게 되면,트랜잭션이 재시도 로직을 포함해서 묶이면서, version 차이로 재시도를 할 때 새로운 버전으로 Stock을조회해와야 하는데, 아직 Transactional이 유지되고 있으니깐, Entity Manager에 있는 이전에 실패한 version의 Stock을 가지고 다시 update를 실패해서 무한루프에 빠진다고 생각했습니다.
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
쓰레드풀에서 쓰레드를 재사용할 수 있는 이유가 궁금합니다.
강의 초반부 스레드는 start해서 작업이 끝나면 재사용할 수 없다고 하셨고 자바독에서도 아래와 같이 start()가 재사용되는 것은 legal하지 않다고 표현하고 있습니다It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.그런데 스레드풀 재사용 시에는 뭔가 다른 작업이 있을까 해서 강의와 함깨 디버깅해보니 addWorker()에서도 Worker에 할당된 스레드의 start()메소드로 호출하고 있었습니다. 스레드 풀의 경우 start()호출 후 메소드가 종료되었음에도 스레드를 재사용할 수 있는 이유는 무엇인가요? 질문을 작성하고 나서 조금 더 고민해보니 아래와 같은 결론에 도달했습니다.(혹시 틀린 내용이 있다면 수정 의견 부탁드립니다) 비슷한 고민을 하신 분이 있을 것 같아 글을 남겨둡니다.생성된 스레드의 start() 실행 -> Worker의 run()실행 -> Worker의 runWorker()실행 -> while()조건에 의해 무한 루프무한루프가 되는 이유 : getTask()를 통해 큐에서 작업을 꺼내와 실행하고 큐에 작업이 없을 경우 블록킹되므로 블로킹이 해제된 시점에서는 task변수에 작업이 할당되어 while조건문이 true가 됨
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
사이드 프로젝트 관련 질문 입니다.
안녕하세요 강사님 좋은 강의 만들어 주셔서 감사합니다. 저는 무엇인가를 만들어보며 학습하는것을 선호 하는데요. 그래서 혹시 이 강의를 통해 얻게된 지식과 관련된 사이드 프로젝트 주제가 있다면 여쭤보고 싶습니다. 감사합니다.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
saveAndFlush 와 synchronized
https://www.inflearn.com/questions/655574위에 질문에서 saveAndFlush를 사용한 이유에 "save 메소드를 사용하게 된다면 데이터베이스에 바로 flush 가 되는것이 아니기때문에 synchronized 를 이용한 방법을 테스트할 때 오류가 날것입니다." 라고 답변해주셨는데 save를 사용하든 saveAndFlush를 사용하든 문제가 발생하지 않나요?flush를 바로 해준다고 데이터베이스에 커밋이 되는게 아니기때문에 충분히 동시성 문제가 생길 수 있다고 생각됩니다. 결국 아래 코드에서 saveAndFlush를 사용하든 save를 사용하든 실패하는것인데 왜 saveAndFlush를 사용하면 오류가 안난다고 하신건지 궁금합니다.@Transactional public synchronized void decrease(Long id, Long quantity) { Stock stock = stockRepository.findById(id).orElseThrow(); stock.decrease(quantity); stockRepository.saveAndFlush(stock); }
-
미해결자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1]
interrupt() 강의 보다 정말 궁금한게 생겨서 문의 남깁니다.
안녕하세요 강사님강의를 듣다가 이상한 것을 발견하여 문의 드립니다. Java Thread Fundamentals - 스레드 기본 API > interrupt() 강의중 InterruptExample 부분의 샘플 코드를 작성하고 테스트 및 정리를 하고 있습니다. 제가 현업에서 사용하는 JDK 11 을 사용해서 연습중에 이상한 현상을 발견하였습니다.JDK 17에서는 문제 없음을 확인하였습니다. 먼저 코드 구현체 부분입니다.public class TreadInterrupt { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println("thread 1 start"); System.out.println("thread 1 isInterrupted() = " + Thread.currentThread().isInterrupted()); }); Thread t2 = new Thread(() -> { System.out.println("thread 2 start"); t1.interrupt(); System.out.println("thread 1 isInterrupted() = " + t1.isInterrupted()); System.out.println("thread 2 isInterrupted() = " + Thread.currentThread().isInterrupted()); }); t2.start(); Thread.sleep(1000); t1.start(); t1.join(); t2.join(); System.out.println("작업 완려"); } } Thread#interrupt(), Thread#isInterrupted() 의 jdk 11과 17 버전입니다. // jdk 11 public class Thread { public boolean isInterrupted() { return isInterrupted(false); } @HotSpotIntrinsicCandidate private native boolean isInterrupted(boolean ClearInterrupted); public void interrupt() { if (this != currentThread()) { this.checkAccess(); synchronized(this.blockerLock) { Interruptible b = this.blocker; if (b != null) { this.interrupt0(); b.interrupt(this); return; } } } this.interrupt0(); } private native void interrupt0(); } // jdk 17 public class Thread { private volatile boolean interrupted; public boolean isInterrupted() { return interrupted; } public void interrupt() { if (this != Thread.currentThread()) { checkAccess(); // thread may be blocked in an I/O operation synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupted = true; interrupt0(); // inform VM of interrupt b.interrupt(this); return; } } } interrupted = true; // inform VM of interrupt interrupt0(); } }위 코드와 같이 jdk 17 은 Thread 객체 내 interrupted 필드를 둠으로써 인터럽트의 상태를 관리하지만 jdk 11 같은 경우에는 native 메서드를 이용해서 인터럽트의 상태를 관리하는 것을 확인할 수 있었습니다. 이 때 jdk 11에서 위 코드를 실행시키면 결과가 아래 와 같이 출력되는것을 확인하였습니다. thread 2 start thread 1 isInterrupted() = false thread 2 isInterrupted() = false thread 1 start thread 1 isInterrupted() = false 작업 완려프로세스 실행을 반복하거나 sleep 의 시간을 늘려보아도 항상 아래와 같은 동일한 결과가 나왔습니다. 하지만 이 코드를 디버그 모드를 이용해서 동작시킨 경우 완전히 다른 결과가 나왔습니다.break point 는 t1.interrupt() 부분에 걸었으며 실제 인터럽트 내부 코드 동작 부분은 확인하지 않고 F8 을 이용해서 코드라인을 프로세스가 종료할 때 까지 넘김thread 2 start thread 1 start thread 1 isInterrupted() = true thread 1 isInterrupted() = true thread 2 isInterrupted() = false위는 실제로 디버그 모드로 동작 시켰을때 처리 결과입니다. 아무리 생각해도 동일한 코드, 동일한 로직인데 디버그 모드일때 정상적으로 출력되고 일반적으로 실행하였을때 인터럽트의 상태가 모두 false 가 나온다는 것이 이해되지 않아 문의 남겨봅니다.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
네임드락에서 부모 트랜잭션과 별도로 실행해야하는 이유
네임드락에서 Propagation.REQUIRES_NEW을 설정하는 이유로 부모 트랜잭션과 별도로 실행되어야 한다고 하셨는데, 왜 별도로 실행해야하는지 궁금합니다.다른분들 질문에서 아래처럼 답변해주셨는데 제가 이해한바가 맞을까요?부모의 트랜잭션과 동일한 범위로 묶인다면 Synchronized 와 같은 문제가 발생합니다. Database 에 commit 되기전에 락이 풀리는 현상이 발생합니다. 그렇기때문에 별도의 트랜잭션으로 분리를 해주어 Database 에 정상적으로 commit 이 된 이후에 락을 해제하는것을 의도하였습니다. 핵심은 lock 을 해제하기전에 Database 에 commit 이 되도록 하는것입니다. Synchronized와 같은 문제 : 트랜잭션이 시작 -> 락 획득 -> 로직 수행 -> 락 반납 -> 트랜잭션 커밋과 같이 커밋되기 전에 락 반납한 상황에서 다른 요청이 들어올 수 있기 때문에 동시성 이슈는 여전히 발생위의 내용대로라면 부모 트랜잭션의 로직은 보류해두고, 동시성이 발생하는 로직만 따로 분리해서 새로운 트랜잭션으로 로직 실행후 commit하고, 부모 트랜잭션의 나머지 로직 수행으로 이해했는데, 제가 응용한 프로젝트는 왜 네임드락의 정합성이 안맞는지 궁금합니다..동시성 테스트할때 시간도 엄청 오래걸립니다.2번 질문에 이어서 제가 위에서 이해한대로 트랜잭션 로그도 확인해봤는데, 부모 트랜잭션 이후로 새로운 트랜잭션 완료한 뒤 기존 트랜잭션을 처리하는걸로 로직은 잘 동작하는데 부정합이 이유가 무엇일까요..?20240302 22:10:41.691 [http-nio-8080-exec-1] INFO o.a.c.c.C.[.[.[/] - Initializing Spring DispatcherServlet 'dispatcherServlet' 20240302 22:10:41.691 [http-nio-8080-exec-1] INFO o.s.w.s.DispatcherServlet - Initializing Servlet 'dispatcherServlet' 20240302 22:10:41.695 [http-nio-8080-exec-1] INFO o.s.w.s.DispatcherServlet - Completed initialization in 4 ms 20240302 22:10:41.957 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.flab.offcoupon.service.CouponIssueService.issueCoupon] 20240302 22:10:41.957 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - 트랜잭션 1 : 쿠폰 발급 요청. eventId : 1, couponId : 1, memberId : 1 20240302 22:10:41.988 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. SELECT id, category, description, start_date, end_date, daily_issue_start_time, daily_issue_end_time, created_at, updated_at FROM event WHERE id = 1; {executed in 14 msec} 20240302 22:10:41.998 [http-nio-8080-exec-1] INFO j.resultsettable - |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------| |id |category |description |start_date |end_date |daily_issue_start_time |daily_issue_end_time |created_at |updated_at | |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------| |1 |바디케어 |바디케어 전품목 할인 |2024-02-27 |2024-02-29 |13:00:00 |15:00:00 |2024-02-27T22:34:01 |2024-02-27T22:33:57 | |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------| 20240302 22:10:42.012 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. SELECT GET_LOCK('namedLock', 3000); {executed in 9 msec} 20240302 22:10:42.013 [http-nio-8080-exec-1] INFO j.resultsettable - |----------------------------| |get_lock('namedlock', 3000) | |----------------------------| |1 | |----------------------------| 20240302 22:10:42.014 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - getLock = 1 20240302 22:10:42.014 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.flab.offcoupon.repository.IncreaseIssuedCoupon.increaseIssuedCouponQuantity] 20240302 22:10:42.014 [http-nio-8080-exec-1] INFO c.f.o.r.IncreaseIssuedCoupon - 트랜잭션 2 쿠폰 발급 수 증가. couponId : 1 20240302 22:10:42.015 [http-nio-8080-exec-1] INFO c.f.o.r.IncreaseIssuedCoupon - 트랜잭션 2 쿠폰 조회. couponId : 1 20240302 22:10:42.029 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. SELECT id, event_id, discount_type, discount_rate, discount_price, coupon_type, max_quantity, issued_quantity, validate_start_date, validate_end_date, created_at, updated_at, version FROM coupon WHERE id = 1; {executed in 13 msec} 20240302 22:10:42.041 [http-nio-8080-exec-1] INFO j.resultsettable - |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------| |id |event_id |discount_type |discount_rate |discount_price |coupon_type |max_quantity |issued_quantity |validate_start_date |validate_end_date |created_at |updated_at |version | |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------| |1 |1 |PERCENT |50 |[null] |FIRST_COME_FIRST_SERVED |500 |10 |2024-02-01T00:00 |2024-02-05T00:00 |2024-02-01T00:00 |2024-02-01T00:00 |0 | |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------| 20240302 22:10:42.058 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. UPDATE coupon SET issued_quantity = 11 WHERE id = 1 {executed in 15 msec} 20240302 22:10:42.059 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.flab.offcoupon.repository.IncreaseIssuedCoupon.increaseIssuedCouponQuantity] 20240302 22:10:42.090 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. SELECT RELEASE_LOCK('namedLock'); {executed in 29 msec} 20240302 22:10:42.092 [http-nio-8080-exec-1] INFO j.resultsettable - |--------------------------| |release_lock('namedlock') | |--------------------------| |1 | |--------------------------| 20240302 22:10:42.093 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - releaseLock = 1 20240302 22:10:42.138 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. SELECT EXISTS (SELECT 1 FROM coupon_issue WHERE member_id = 1 AND coupon_id = 1 AND DATE (created_at) = 2024-02-29 LIMIT 1); {executed in 25 msec} 20240302 22:10:42.139 [http-nio-8080-exec-1] INFO j.resultsettable - |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |exists (select 1 from coupon_issue where member_id = 1 and coupon_id = 1 and date (created_at) = '2024-02-29' limit 1) | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |false | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 20240302 22:10:42.176 [http-nio-8080-exec-1] DEBUG j.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) 1. INSERT INTO coupon_issue (member_id, coupon_id, coupon_status, created_at, updated_at) VALUES (1, 1, 'NOT_ACTIVE', 2024-03-02T22:10:42.141176, 2024-03-02T22:10:42.141176) {executed in 32 msec} 20240302 22:10:42.178 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.flab.offcoupon.service.CouponIssueService.issueCoupon]
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
NamedLock 에서 @Transactional 사용에 관한 질문
NamedLock을 사용한 예제실습 중 질문입니다NamedLockStockFacade의 decrease 메소드에 @Transactional 을 걸고 StockService의 decrease 메소드에는 @Transactional 을 걸지 않아도 잘 작동해야하는거 같은데, 데드락에 걸리는거 같습니다. 그 이유가 궁금합니다제 생각에는 NamedLockStockFacade의decrease 메소드에 @Transactional 을 걸면,lockRepository.getLock(id.toString()), stockService.decrease(id, quantity), lockRepository.releaseLock(id.toString()) 이 3 메소드가 모두 한 트랜잭션 안에서 처리되므로 lock을 걸고 lock을 해제하는 그 사이에 재고를 감소하므로 아무 문제가 없어보여서 질문드립니다.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
충돌의 기준이 무엇인가여?
Optimistic Lock은 충돌이 빈번할 경우 적절하지 않다고 하는데, 충돌이 기준이 어떤건지 궁금합니다. 별도로 프로젝트에서 Mybatis랑 같이 쿠폰발급 프로젝트에서 응용중입니다.전 쿠폰 500개를 기준으로 발급된 수량을 합산하는 로직입니다.스레드 100개기준으로 동시성테스트를 진행했을때 69개만 적용되더라구요. 재시도 로직에서 Thread.sleep(1000)으로 하면 98개까지 적용됩니다. 그런데 스레드 10개를 기준으로 동시성 테스트를 진행했을때는 정합성이 맞고, 스레드1000개를 돌려버리면 500개가 전부다 발급됩니다. 1000개 중에 몇개는 실패하고, 몇개는 성공하고 그래서 500개 전부 다 발급된 상황이라고 예상됩니다.Q1. 충돌이 많은 상황이라는게 동시에 여러 스레드가 접근하는 걸 의미하는게 맞나요? 충돌의 기준은 무엇인지, 어플리케이션 내부 로직마다 다른걸까요?Q2. 충돌이 빈번하지 않을때 낙관적락을 사용하는 경우가 어떤 경우인지 궁금합니다.. 정합성이 많이 떨어진다고 생각하는데 실제로 현업에서 많이 사용하나요?Q3. 데이터베이스에 락을 걸지 않아서 성능상 이점이 있다고 하셨는데, 제가 테스트해봤을때는 pessimisticLock보다 Mysql에 더 부하가 많이가더라구요. 재시도로직으로 I/O작업이 더 많이 일어나서 그런것 같은데, 말씀하시는 '성능상 이점'이라는건 어떤건지 궁금합니다.