목적
- 서비스에서 처리량이 증가하거나 작업 시간이 길어지는 상황에서
기존의 동기식 직접 DB 접근방식이 가진 한계를 확인하고
Kafka 기반 비동기 처리 구조의 성능 및 안정성을 확인하기 위함
실험환경
- 애플리케이션Kotlin Spring Boot 기반 API 서버
- DB: MariaDB
- Kafka: 단일 노드
- 트랜잭션 타임아웃: 1분
- 인위적 딜레이: 일부 실험에서 0.3초 지연 삽입
- 성능 측정 도구: k6
- vus: 1000(가상 유저 1000명)
- durations: 5m (총 5분간 실행)
- iterations: 3000 (총 요청 수:3000)
측정 기준
- 처리 시간 (diffSec)
- inventory: 첫 번째 행의 createdAt과 마지막 행의 createdAt의 차이
- order: 동일한 행의 createdAt과 updatedAt의 차이
- 실패율 (Failure Rate)
- 부하 테스트 도구 k6 기준
- rate: 실패율 (0.0 = 모두 성공, 1.0 = 모두 실패)
- passes: 실패 요청 수 (예: HTTP 오류)
- fails: 성공 요청 수
- 추가적으로 DB에 실제 반영된 quantity 값도 함께 검토
실험 결과 요약
Inventory 삽입 (3000건)
실험 | 방식 | 파티션/배치 | 락 | 처리 시간 (초) | 실패율 | 결과 |
---|---|---|---|---|---|---|
inventory1 | 직접 DB 접근 | - | 없음 | 30 | 0.0 | ✅ |
inventory2 | Kafka + 단일 파티션 | 단건 | 없음 | 34 | 0.0 | ✅ |
inventory3 | Kafka + 3파티션 | 단건 | 없음 | 27 | 0.0 | ✅ |
inventory4 | Kafka + 3파티션 | 배치(20건) | 없음 | 18 | 0.0 | ✅ |
단일 Order UPDATE - 딜레이 없음 (최초 삽입 1건, 갱신 3000건)
실험 | 방식 | 락 | Kafka 파티션 | 처리 시간 (초) | 실패율 | 결과 |
---|---|---|---|---|---|---|
order1 | 직접 DB 접근 | 비관적 락 | 없음 | 49 | 0.0 | ✅ |
order2 | Kafka 컨슈머 | 없음 | 1개 | 52 | 0.0 | ✅ |
order3 | Kafka 컨슈머 | 비관적 락 | 3개 | 38 | 0.0 | ✅ |
단일 Order UPDATE - 딜레이 0.3초 (타임아웃 1분, 최초 삽입 1건, 갱신 3000건)
실험 | 방식 | 락 | Kafka 파티션 | 처리 시간 (초) | 처리된 quantity | 실패율 | 결과 |
---|---|---|---|---|---|---|---|
order1 | 직접 DB 접근 | 비관적 락 | 없음 | 303 | 328 | 0.972 | ❌ |
order2 | Kafka 컨슈머 | 없음 | 1개 | 384 | 3001 | 0.0 | ✅ |
order3 | Kafka 컨슈머 | 비관적 락 | 3개 | 333 | 3001 | 0.0 | ✅ |
실패율 상세
- k6 실패율 계산 기준
- rate: 실패율 (0.0 = 100% 성공, 1.0 = 100% 실패)
- passes: 실패 요청 수 (예: HTTP 500 등)
- fails: 성공 요청 수
- 실패율 = passes / (passes + fails)
- 실패 발생 실험: order1(직접 DB 접근 + 딜레이)
- 요청 기준 성공률: 약 2.8%(84/3001)
- 실제 DB 반영 성공 수량: 328건
- 일부 실패 응답이 DB에는 성공적으로 반영된 것으로 추정
결론
- Kafka 기반의 비동기 처리 구조는 직접 DB 접근 방식에 비해
다음의 이점을 갖는다. - 높은 안정성
- 직접 DB 접근 방식은 요청이 몰릴수록 트랜잭션 충돌, 락 경합, 커넥션 부족등의 이슈로 인해 실패율이 높아지는 경향이 있다.
- 특히 급격하게 트래픽이 몰려 DB 커넥션이 부족해지고 트랜잭션 처리 시간이 길어질 경우 실패율이 급격히 올라가며, 동시에 DB 부하도 높아져 다른 기능에 영향을 준다.
- 반면 Kafka 비동기 구조에서는 모든 요청을 큐잉하고 순차적으로 처리하기 때문에 요청량이 일시적으로 급증하더라도 상대적으로 안정적으로 처리 흐름을 유지할 수 있다.
- 수평적 확장성
- Kafka는 파티션을 추가 및 컨슈머 수를 늘리고,
배치 처리(batch)를 도입함으로써
처리 성능을 선형적으로 확장할 수 있다. - 실제 실험에서도 Kafka 파티션 수를 늘리고 배치 처리까지 도입하자
처리 시간(diffSec)이 눈에 띄게 줄어들었다. - 즉, 트래픽이 늘어나더라도 유연하게 대응할 수 있는 구조이다.
- Kafka는 파티션을 추가 및 컨슈머 수를 늘리고,
- DB 부하 감소 및 시스템 전반의 응답성 향상
- Kafka 구조는 비동기 방식으로 동작하기 때문에, 사용자 요청 시점에 DB에 직접 접근하지 않는다.
- 내부 처리 과정에서 병목이 발생하더라도, 요청을 Kafka가 우선 수신하고 처리하므로 DB에는 상대적으로 여유 있는 요청만 전달된다.
- 이로 인해 과도한 동시 요청이 DB에 몰리는 상황을 효과적으로 방지할 수 있다.
- 실험 결과에서도 이런 차이는 명확히 드러난다.
- 직접 DB 접근 방식에서는 트래픽이 몰려 DB 타임아웃이 발생한 이후부터는 조회 요청까지 큰 지연을 겪었다.
- 반면 Kafka 기반 구조는 처리 작업과 조회 요청이 서로 영향을 받지 않기 때문에, 초기 몇 초를 제외하고는 조회 응답 시간이 안정적으로 유지되었다.
번외 - 스티키 파티셔너 vs 라운드로빈 파티셔너
기존 실험에서는 프로듀서에서 파티션 키를 설정하지 않았기 때문에
기본 설정인 스티키 파티셔너를 사용했다.이번에는 파티션을 3개 사용하는 테스트 시나리오만 모아,
프로듀서에서 랜덤 키를 설정하여 라운드로빈 파티셔너를 사용하도록 실험하였다.두 파티셔너 간의 처리 시간 차이 비교
항목 Sticky 파티셔너 Round-Robin 파티셔너 차이 Order3 (딜레이 없음, 갱신) 38초 39초 +1초 Order3 (딜레이 있음, 갱신) 333초 346초 +13초 Inventory3 (파티션 3개, 삽입) 27초 32초 +5초 Inventory4 (배치 삽입) 18초 25초 +7초 모든 항목에서 Sticky 파티셔너가 Round-Robin 파티셔너보다 더 빠른 처리 속도를 보였다.