전체 글 42

[동시성 실험] Queue를 붙이면 예약 API가 빨라질 줄 알았다 -9

Queue를 붙이면 예약 API는 빨라질 거라고 생각했다. 앞의 실험에서는 같은 좌석에 요청이 몰릴 때마다 누군가 기다려야 했다. 비관적 락은 DB lock 앞에서 기다렸고, Redis 분산 락은 Redis key 앞에서 기다렸다. 특히 Redis 분산 락의 정상 멀티 실험은 중복 예약을 막았지만 p95가 7.49초였다. watchdog 조건에서는 p95가 12.60초까지 올라갔다. 그래서 마지막 6단계에서는 기다리는 위치를 바꿔보기로 했다.사용자의 HTTP 요청이 예약 완료까지 기다리지 않고, 일단 Kafka에 요청을 넣은 뒤 consumer가 나중에 처리하도록 만들었다. API는 접수만 하고 끝나니 응답도 금방 내려갈 것 같았다. 측정 전 예상은 enqueue p95 300ms 이하였다.실제 정상 부..

JavaSpring 2026.07.11

[동시성 실험] Redis 락도 만료되면 깨진다 -8

이전 글에서는 Redis 분산 락이 정상일 때를 봤다.결과는 괜찮았다. 단일 인스턴스에서도, 멀티 인스턴스에서도 성공 예약은 1건이었다. 초과 성공은 0건이었다. synchronized와 달리 JVM이 여러 개여도 같은 Redis key를 바라보니까 멀티 인스턴스 중복 예약을 막을 수 있었다.하지만 정상 상황만 보면 Redis lock의 절반만 본 것이다.운영에서 더 중요한 질문은 따로 있다.Redis가 죽으면 어떻게 될까?그리고 하나 더.Redis lock을 잡았는데, 처리 도중 lock TTL이 먼저 끝나면 어떻게 될까?이번 글에서는 그 실패 조건을 본다.먼저 용어부터 정리하자Redis lock을 볼 때 헷갈리기 쉬운 값이 세 개 있었다.waitTime, leaseTime, watchdog이다.첫 번..

JavaSpring 2026.07.05

[동시성 실험] Redis 분산 락을 쓰면 멀티 인스턴스 문제를 해결할 수 있을까? -7

앞의 실험들에서 계속 같은 질문으로 돌아왔다. 앱 인스턴스가 여러 대일 때, 같은 좌석에 대한 요청을 어디서 줄 세워야 할까?2단계에서 synchronized를 써봤지만 멀티 인스턴스에서는 깨졌다. JVM이 두 개면 lock도 두 개였기 때문이다.3단계에서 비관적 락을 써봤다. DB의 SELECT ... FOR UPDATE로 같은 좌석 row를 잠그니까 단일과 멀티 모두 중복 예약은 막혔다. 하지만 락 대기와 커넥션 풀 병목이 같이 따라왔다.4단계에서 낙관적 락도 봤다. @Version을 붙였지만 인기 좌석에서는 예상처럼 "전부 버전 충돌"이 나지 않았다. 오히려 FK 때문에 생긴 S-lock -> X-lock deadlock이 더 크게 보였다. 그래서 5단계에서는 락을 DB 밖으로 빼보기로 했다.Red..

JavaSpring 2026.06.28

[카데미] 내가 공부하고픈 플래시 카드 생성을 AI에게 맡긴 날

카드 생성 반복 작업을 없애려고 AI를 붙인 하루학습지 만들기는 굳건히 노가다였다. 카드는 앞면=뒷면=설명 형식으로 등록해야 했고, 내가 공부하고 싶은 문제를 전부 그 형식으로 바꿔서 입력해야 했다.예를 들어 영어 단어 30개가 있으면 30개를 모두 단어=뜻=설명 형태로 정리해야 했다. 공부할 내용이 아직 없을 때는 더 번거로웠다. 먼저 다른 AI 서비스에서 "TOEIC 빈출 동사 30개 뜻이랑 설명까지 정리해줘"라고 시킨 다음, 그 결과를 다시 카데미 형식에 맞춰 붙여넣어 문항을 만들어야 했다."이 변환이랑 붙여넣기를 굳이 내가 해야 해?"결심에서 구현까지 같은 날이날은 사이드 프로젝트 통틀어 가장 빠르게 굴러간 날이었다. 커밋만 13개가 찍혔다.먼저 설계 문서부터 썼다. 사용자가 프롬프트를 입력하면,..

[동시성 실험] 낙관적 락으로 막으면 다 버전 충돌일 줄 알았는데, 정작 deadlock이 터졌다 -6

지난 3단계에서 비관적 락(SELECT ... FOR UPDATE)을 써봤다. 정합성은 완벽했는데, 한 가지가 계속 걸렸다.충돌이 하나도 없어도 일단 락부터 잡고 들어간다는 점이다. 인기 좌석이든 아무도 안 보는 좌석이든, 모든 예약이 줄을 서서 X-lock을 잡고 commit까지 들고 있었다.그래서 4단계는 낙관적 락(@Version)으로 넘어왔다. 낙관적 락의 아이디어는 단순하다."일단 충돌 안 날 거라고 보고 그냥 진행하다가, 커밋할 때 누가 먼저 바꿨으면 그때 실패시킨다."선점 락을 안 잡으니까, 충돌이 없을 땐 비관적 락보다 쌀 거라는 기대였다.처음 예상시나리오는 두 개로 잡았다.시나리오 A: 좌석 1개에 2,000명이 동시에 (인기 좌석, 고경합)시나리오 B: 좌석 100개에 2,000명을 분..

JavaSpring 2026.06.14

[동시성 실험] 비관적 락을 걸었더니 중복은 막혔고, 병목은 다른 곳에서 보였다 -5

앞의 두 실험은 예상과 꽤 다르게 흘러갔다. 1단계 No Lock에서는 중복 예약이 터질 줄 알았다. 그런데 중복 예약은 생기지 않았고, 대신 MySQL InnoDB의 deadlock이 나왔다.2단계에서는 synchronized를 걸었다. 단일 JVM에서는 한 번에 한 요청만 들어오니까 이제 안전할 줄 알았다. 하지만 @Transactional commit이 synchronized lock 해제 뒤에 일어나면서 race window가 생겼고, 성공 예약이 2건 남았다. 멀티 인스턴스에서는 더 직접적으로 깨졌다. JVM이 두 개면 synchronized lock도 두 개였기 때문이다. 그래서 3단계에서는 락의 위치를 애플리케이션 밖으로 옮겼다.DB에 락을 건다. 여기서부터는 락 전략이 크게 두 갈래로 나뉜..

JavaSpring 2026.06.03

[동시성 실험] synchronized는 JVM이 두 개면 어떻게 깨질까? -4

앞 글에서는 시나리오 1, 단일 인스턴스에서 synchronized를 걸어봤다. 결과는 예상보다 복잡했다.JVM 하나 안에서는 메서드 진입이 직렬화되면서 deadlock은 줄었다. 하지만 @Transactional 프록시 구조 때문에 synchronized lock이 풀린 뒤 commit 전 race window가 열렸고, 성공 예약이 2건 생겼다.이번 글에서는 시나리오 2를 본다.멀티 인스턴스다.k6 → nginx → Spring JVM 2개 → MySQL 운영 환경에서는 앱을 하나만 띄우지 않는 경우가 많다.트래픽을 나누거나 무중단 배포를 하려면 같은 Spring 앱을 여러 개 띄우고, 앞단의 로드밸런서가 요청을 분산한다.문제는 synchronized가 JVM 안에서만 통한다는 점이다.Spring 앱..

JavaSpring 2026.05.31

[동시성 실험] synchronized는 단일 JVM에서도 안전할까? -3

1단계에서는 락을 일부러 걸지 않았다. 그런데 중복 예약은 생기지 않았다. 대신 MySQL InnoDB의 row-level lock과 FK 검증이 얽히면서 deadlock이 발생했다.처음에는 이 결과를 보고 이렇게 생각했다.그럼 애플리케이션에서 먼저 직렬화하면 되지 않을까? 그래서 2단계에서는 가장 단순한 방법부터 써봤다.synchronized Java 메서드에 키워드 하나만 붙이면, 같은 JVM 안에서는 한 번에 한 스레드만 그 메서드에 들어갈 수 있다.좌석 예약처럼 "한 번에 하나만 처리하면 되는" 로직에는 꽤 그럴듯해 보인다.이번 글에서는 먼저 시나리오 1, 단일 인스턴스 실험만 본다.k6 → Spring JVM 1개 → MySQL처음 예상은 단순했다.단일 JVM에서는 synchronized가 요청..

JavaSpring 2026.05.31

[카데미] 나만 쓰던 학습지를 다른 사람에게 열기까지

3월 한 달간은 거의 나 혼자만의 학습지였다. 내가 만들고, 내가 외우는 것.4월 들어 생각이 바뀐 계기는 별 거 아니었다. 내가 잘 사용하고 있는데, 다른 사람들도 잘 쓰지 않을까? 그게 시작이었다."공개"라는 한 글자기능을 어떻게 풀지 며칠을 굴렸다. 결국 두 개의 키워드로 압축됐다.공개: 내가 만든 학습지를 다른 사용자도 볼 수 있게 켜놓을 수 있다.복제(Fork): 다른 사람의 공개 학습지를 내 학습지로 가져와 쓸 수 있다.복제가 핵심이었다. 단순 열람만 되면 결국 "내 카드"에 안 들어와서 학습 사이클에 안 붙는다. 복제하는 순간부터는 그 학습지는 내 것이 돼야 했다.DB가 약간 자라났다학습지 테이블에 컬럼 세 개가 추가됐다.is_public — 공개 여부fork_count — 몇 명이 복제해 ..

[동시성 실험] 락 없이 예약하면 중복될 줄 알았는데, 결과가 이상했다 -2

동시성 실험의 첫 번째 단계는 "No Lock"이었다.애플리케이션 코드에서는 명시적인 락을 걸지 않고 예약 API를 구현한 다음, 같은 좌석에 1,000명이 동시에 요청을 보내보는 실험이다.처음 가설은 아주 단순했다.트랜잭션만으로는 read-modify-write race condition을 막을 수 없으니, 같은 좌석에 성공 예약이 2건 이상 생길 것이다.솔직히 이건 거의 당연히 그렇게 나올 줄 알았다. 좌석을 조회하고, 아직 AVAILABLE이면 RESERVED로 바꾸는 코드니까, 동시에 여러 요청이 같은 값을 읽으면 중복 예약이 터질 거라고 생각했다.그런데 실제 결과는 예상과 달랐다.중복 예약은 발생하지 않았다.대신 다른 문제가 튀어나왔다.실험 코드1단계의 예약 로직은 일부러 단순하게 만들었다.@T..

JavaSpring 2026.05.24
반응형