6. Jmeter 를 활용한 Kafka 병목 현상 테스트

6.1. prodcer/consumer 수정 및 주문 API 서비스 로직 수정

6.1.1. OrderProducer/OrderConsumer 수정

굳이 객체 전체를 넘기지 않아도 되므로 메세지 value 값도 string(unique 한 주문번호) 으로 수정

(필요에 따라 application.yml 의 기본 producer/consumer 설정 변경하거나, String 타입의 별도 kafka factory 설정 추가)

  • OrderProducer
private final KafkaTemplate<String, String> kafkaTemplate;

public void sendOrder(String orderNumber) {
    kafkaTemplate.send(TOPIC, orderNumber);
}
  • OrderConsumer
    private static final String TOPIC = "orders";

    private final KafkaTemplate<String, String> kafkaTemplate;

    public void sendOrder(String orderNumber) {
        kafkaTemplate.send(TOPIC, orderNumber);
    }

 

6.1.2. OrderService 수정

Kafka 메세지 전송 전에 PROCESSING(진행중) 상태로 주문데이터 저장 로직 추가

 -> 상태 전이 관리하여 중복 요청 방지를 위해

디버깅 로그 추가

package com.toy.service;

import com.toy.dto.ApiResponseDto;
import com.toy.dto.OrderRequestDto;
import com.toy.kafka.OrderProducer;
import com.toy.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final OrderProducer orderProducer;

    public ApiResponseDto order(OrderRequestDto orderRequestDto) {
        log.debug("Order Request !! [" + orderRequestDto.getOrderNumber() + "]");

        orderRepository.save(orderRequestDto.toEntity());
        orderProducer.sendOrder(orderRequestDto.getOrderNumber());

        return ApiResponseDto.builder().result(true).message("주문 등록 요청 성공").build();
    }
}

 

 

6.2. 주문 저장 로직에 병목 현상 구현

6.2.1. repository 에 조건부 상태 변경 추가

지정 한 상태일 때만 상태가 변경 될 수 있도록 JPQL 추가

package com.toy.repository;

import com.toy.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;


@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    @Modifying
    @Query("UPDATE Order o SET o.status = :newStatus WHERE o.orderNumber = :orderNumber AND o.status = :expectedStatus")
    int updateStatusConditionally(@Param("orderNumber") String orderNumber,
                                  @Param("expectedStatus") Order.OrderStatus expectedStatus,
                                  @Param("newStatus") Order.OrderStatus newStatus);
}

 

 

6.2.2. 주문 저장 로직 변경

병목 현상 구현을 위해 2초 sleep 추가

주문 상태가 PROCESSING(진행중) 이면 DONE(완료) 처리하여 저장

 -> 주문 상태 완료 update 결과가 0인 경우 이미 처리 된 주문으로 가정하여 debug log 찍어두기

 

package com.toy.service;

import com.toy.entity.Order;
import com.toy.repository.OrderRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
@RequiredArgsConstructor
public class OrderStorageService {
    private final OrderRepository orderRepository;

	@Transactional
    public void store(String orderNumber) {
        log.debug("Order Store Start !! [" + orderNumber + "]");

        try {
            // 비즈니스 처리 소요 시간 가정 2초
            Thread.sleep(2000);

            int result = orderRepository.updateStatusConditionally(orderNumber, Order.OrderStatus.PROCESSING, Order.OrderStatus.DONE);

            if(result == 0) {
                log.debug("Already Stored Order :( [" + orderNumber + "]");
                return;
            }

        } catch (Exception e) {
            orderRepository.updateStatusConditionally(orderNumber, Order.OrderStatus.PROCESSING, Order.OrderStatus.FAILED);
        }

        log.debug("Order Store End !! [" + orderNumber + "]");
    }
}

 

 

6.3. Jmeter 파일 다운로드

Jmeter 공식 다운로드 페이지 접속하여 압축파일 다운로드

https://jmeter.apache.org/download_jmeter.cgi

 

 

6.4. Jmeter 실행

압축 해제 한 Jmeter 폴더\bin\jmeter.bat 클릭 하여 Jmeter GUI 실행

 

 

6.5. Jmeter 설정

6.5.1. Thread Group 추가

Test Plan 우클릭 > Add > Threads (Users) > Thread Group

1초 동안 사용자 10명 동시 요청 환경 설정

  • Number of Threads : Thread 수 지정 (동시 요청 사용자 수)
  • Ramp-up period : 요청 텀 지정
  • Loop Count : 반복 횟수 지정

 

6.5.2. Http Request 추가

Thread Group 우클릭 > Add > Sampler > Http Request

API Url, method 설정

Jmeter 함수 활용하여 Request Body 설정

  • __time(yyyyMMddHHmmss) : 현재 시간 문자열 변환
  • __threadNum : 스레드 번호 (각각 다른 번호 주기 위해 활용)
{
  "orderNumber": "order_${__time(yyyyMMddHHmmss)}_${__threadNum}",
  "productId": 1,
  "userId": "haley${__threadNum}",
  "quantity": 1
}

 

6.5.3. Http Header Manager 추가

Http Request 우클릭 > Add > Config Element > Http Header Manager

Content-Type : application/json 추가

 

6.5.4. View Results Tree 추가

Thread Group 우클릭 > Add > Listener > View Results Tree

 

6.6. Jmeter 테스트 실행

상단 실행 버튼 클릭하고 View Results Tree 클릭하여

Request 정상 호출 되었는지 확인

 

 

6.7. 병목 현상 확인

OrderService, OrderStorageService 에 찍어둔 debug log 확인

Order API 호출은 0.4초에 10회 모두 요청했지만,

실제 주문 저장 처리는 12분 00초 부터 12분 20초 까지 약 20초 소요

 -> consumer concurrency 기본값은 1 이므로, sleep 2초 * 10회 하여 총 20초

17:11:57.969+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_6]
17:11:57.969+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_2]
17:11:57.969+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_3]
17:11:57.970+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_4]
17:11:57.969+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_7]
17:11:57.969+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_5]
17:11:57.973+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_1]
17:11:58.002+09:00 DEBUG 19192 : Order Request !! [order_20250509171157_8]
17:11:58.185+09:00 DEBUG 19192 : Order Request !! [order_20250509171158_9]
17:11:58.380+09:00 DEBUG 19192 : Order Request !! [order_20250509171158_10]

17:12:00.232+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_4]
17:12:02.465+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_4]
17:12:02.486+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171158_10]
17:12:04.496+09:00 DEBUG 19192 : Order Store End !! [order_20250509171158_10]
17:12:04.519+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_8]
17:12:06.543+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_8]
17:12:06.605+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_7]
17:12:08.627+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_7]
17:12:08.643+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171158_9]
17:12:10.667+09:00 DEBUG 19192 : Order Store End !! [order_20250509171158_9]
17:12:10.699+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_6]
17:12:12.708+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_6]
17:12:12.733+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_2]
17:12:14.756+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_2]
17:12:14.789+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_1]
17:12:16.811+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_1]
17:12:16.840+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_3]
17:12:18.854+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_3]
17:12:18.887+09:00 DEBUG 19192 : Order Store Start !! [order_20250509171157_5]
17:12:20.898+09:00 DEBUG 19192 : Order Store End !! [order_20250509171157_5]
728x90
반응형

+ Recent posts