서비스 요청 시 과도한 시간 소요를 줄이기 위한 노력

2024. 3. 20. 16:52프로젝트/써큘러랩스

728x90
SMALL

프로젝트를 개발하던 도중 1차적으로 완성된 백엔드 api를 개발한 안드로이드 어플리케이션에서 호출할 때 대용량의 데이터를 포함하여 요청 시 얼마나 시간이 걸리고 이에 대한 해결책을 알아보고자 하였습니다.

 

 

기존 서비스 요청 시

- 안드로이드 로직

 // 테스트 용 스캔 데이터 총 1500개
for (int i = 0; i < 500; i++) {
        String rfidChipCode = "RFID" + i + "CHIP";
        String filteringCode = "CCA2310";
        String productCode_ = "A00";
        String productSerialCode_ = (i + 1) + "D240313";

        if (!productCodeList.containsKey(productCode_)) {
            productCodeList.put(productCode_, 1);
        } else {
            productCodeList.put(productCode_, productCodeList.get(productCode_) + 1);
        }

        JSONObject eachTestData = new JSONObject();
        eachTestData.put("rfidChipCode", rfidChipCode);
        eachTestData.put("filteringCode", filteringCode);
        eachTestData.put("productCode", productCode_);
        eachTestData.put("productSerialCode", productSerialCode_);

        sendProductCodes.add(eachTestData);
}

for (int i = 0; i < 500; i++) {
    String rfidChipCode = "RFID" + i + "CHIP";
    String filteringCode = "CCA2310";
    String productCode_ = "AA1";
    String productSerialCode_ = (i + 1) + "D240313";

    if (!productCodeList.containsKey(productCode_)) {
        productCodeList.put(productCode_, 1);
    } else {
        productCodeList.put(productCode_, productCodeList.get(productCode_) + 1);
    }

    JSONObject eachTestData = new JSONObject();
    eachTestData.put("rfidChipCode", rfidChipCode);
    eachTestData.put("filteringCode", filteringCode);
    eachTestData.put("productCode", productCode_);
    eachTestData.put("productSerialCode", productSerialCode_);

    sendProductCodes.add(eachTestData);
}

for (int i = 0; i < 500; i++) {
    String rfidChipCode = "RFID" + i + "CHIP";
    String filteringCode = "CCA2310";
    String productCode_ = "BB1";
    String productSerialCode_ = (i + 1) + "D240313";

    if (!productCodeList.containsKey(productCode_)) {
        productCodeList.put(productCode_, 1);
    } else {
        productCodeList.put(productCode_, productCodeList.get(productCode_) + 1);
    }

    JSONObject eachTestData = new JSONObject();
    eachTestData.put("rfidChipCode", rfidChipCode);
    eachTestData.put("filteringCode", filteringCode);
    eachTestData.put("productCode", productCode_);
    eachTestData.put("productSerialCode", productSerialCode_);

    sendProductCodes.add(eachTestData);
}

String[] productsArray = productCodeList.keySet().toArray(new String[productCodeList.size()]);

for (int j = 0; j < productsArray.length; j++) {
    JSONObject productStatusCount = new JSONObject();
    productStatusCount.put("product", productsArray[j]);
    productStatusCount.put("orderCount", productCodeList.get(productsArray[j]));

    eachProductsSize.add(productStatusCount);
}

///////////////////////////////////////////////////////////////////////////

JSONObject json = new JSONObject();
json.put("machineId", machineId);
json.put("tag", outTag);
json.put("selectClientCode", selectClientCode);
json.put("supplierCode", supplierCode);
json.put("productCodes", new JSONArray(sendProductCodes));
json.put("eachProductCount", new JSONArray(eachProductsSize));

JSONObject requestJson = new JSONObject(json.toString());

// RequestBody 생성
RequestBody body = RequestBody.create(requestJson.toString(), JSON);

// 써큘 개발 서버 용
Request request = new Request.Builder()
        .url("http://{ip 주소}/out")
        .post(body)
        .build();

 

 

- 요청 백엔드 서버 API

@Transactional
public CompletableFuture<RfidScanDataOutResponseDto> sendOutData(RfidScanDataOutRequestDto sendOutDatas) throws InterruptedException {
    log.info("제품 출고 처리 service");

    // 앱에서 넘겨받은 데이터들을 변수에 저장하여 고정
    String deviceCode = sendOutDatas.getMachineId(); // 기기 코드
    String tag = sendOutDatas.getTag(); // 출고 태그 값
    String clientCode = sendOutDatas.getSelectClientCode(); // 고객사 코드
    String supplierCode = sendOutDatas.getSupplierCode(); // 공급사 코드
    List<SendProductCode> receiveProductCodes = sendOutDatas.getProductCodes(); // 스캔한 제품 데이터들의 제품 분류 코드와 각 제품의 시리얼 코드 리스트
    List<SendOrderCount> eachProductCount = sendOutDatas.getEachProductCount(); // 각 제품의 출고 제품 수량

    // 최종 반환 DTO 객체에 넣어줄 리스트객체 생성
    List<ProductDetailResponseDto> productDetailResponseDtos = new ArrayList<>();
    // 실제 RfidScanHistory 엔티티에 같이 매핑되어 저장될 ProductDetail 엔티티 리스트 객체 생성
    List<ProductDetailHistory> rfidHistoryMappingProductDetailHistorties = new ArrayList<>();
    List<RfidScanHistory> rfidScanHistoryList = new ArrayList<>();
    // 최종 반환 DTO 객체 생성
    RfidScanDataOutResponseDto responseDto = null;
    // RFID 기기 스캔 이력 정보
    RfidScanHistory rfidScanHistory = null;

    // 앱으로부터 넘겨 받은 스캔 데이터들의 제품 분류 코드와 각 제품 시리얼 코드 리스트를 하나씩 조회하여 출고 이력 처리
    for (int i = 0; i < receiveProductCodes.size(); i++) {
        if (receiveProductCodes.get(i).getFilteringCode().contains("CCA2310")) {
            // 기존에 저장된 대분류 제품 스캔 이력(ProductDetail)이 존재하는지 확인
            ProductDetail productDetail = productDetailQueryData.checkRemainProductDetail(
                    receiveProductCodes.get(i).getRfidChipCode(), receiveProductCodes.get(i).getProductSerialCode(),
                    receiveProductCodes.get(i).getProductCode(), supplierCode, clientCode);

            // 만약 대분류 제품 스캔 이력(ProductDetail)이 기존에 존재하지 않았을 경우에 각 제품 스캔 이력을 하나씩 저장
            if (productDetail == null) {
                // 제품 스캔 이력
                ProductDetail newInputProductDetail = ProductDetail.builder()
                        .rfidChipCode(receiveProductCodes.get(i).getRfidChipCode())
                        .productSerialCode(receiveProductCodes.get(i).getProductSerialCode())
                        .productCode(receiveProductCodes.get(i).getProductCode())
                        .supplierCode(supplierCode)
                        .clientCode(clientCode)
                        .status("출고")
                        .cycle(0)
                        .latestReadingAt(LocalDateTime.now())
                        .build();

                // 저장
                productDetailRepository.save(newInputProductDetail);

                // 새로 저장된 대분류 제품 스캔 이력(ProductDetail)을 공유하기 위해 if문 바깥으로 공유 처리
                productDetail = newInputProductDetail;

                log.info("productDetail이 저장됨을 확인 : {}", productDetail.getProductDetailId());

            } else { // 만약 대분류 제품 스캔 이력(ProductDetail)이 기존에 존재하는데, 상태가 한 번 사이클이 완료된 상태(대기?) 이면 상태와 최신 리딩 시간을 출고로 변경
                if (!productDetail.getStatus().equals("출고") && !productDetail.getStatus().equals("입고") && !productDetail.getStatus().equals("폐기")) {
                    productDetail = productDetailQueryData.scanOutUpdateStatusAndReadingAt(productDetail);

                    entityManager.flush();
                    entityManager.clear();
                }
            }

            // 최종 확인 용 DTO 객체의 productDetails 속성에 들어갈 ProductDetail 정보 list 데이터 저장
            productDetailResponseDtos.add(
                    ProductDetailResponseDto.builder()
                            .productDetailId(productDetail.getProductDetailId())
                            .rfidChipCode(productDetail.getRfidChipCode())
                            .productSerialCode(productDetail.getProductSerialCode())
                            .productCode(productDetail.getProductCode())
                            .supplierCode(productDetail.getSupplierCode())
                            .clientCode(productDetail.getClientCode())
                            .status(productDetail.getStatus())
                            .cycle(productDetail.getCycle())
                            .latestReadingAt(productDetail.getLatestReadingAt().toString())
                            .build()
            );

            if (productDetail.getStatus().equals("출고")) {

				// 예기치 않은 폐기 제품 수 (RfidScanHIstory의 flowRemainCount를 계산하기 위한 용도)
                int unExpectedDiscardProductCount = 0;

                // 스캔하여 들어온 제품 데이터들을 하나씩 조회
                for (SendProductCode eachProductCode : receiveProductCodes) {
                    // 들어온 제품 데이터의 필터링 코드에 CCA2310가 있어야 하고, 현재 조회한 제품의 코드가 동시에 작업 중인 ProductDetail의 제품 코드와 일치할 경우 진입
                    if (eachProductCode.getFilteringCode().contains("CCA2310") && eachProductCode.getProductCode().equals(productDetail.getProductCode())) {
                        // 이미 스캔한 제품 데이터 중 특정 제품이 폐기 이력에 존재한다면 계산하기 위한 unExpectedDiscardProductCount 변수에 +1
                        if (discardHistoryQueryData.checkProductDiscard(eachProductCode.getProductCode(), eachProductCode.getProductSerialCode())) {
                            unExpectedDiscardProductCount += 1;
                        }
                    }
                }

                // 상태 요청 수량 변수
                int statusCount = 0;

                // 요청 수량 추출 후 변수에 저장
                for (SendOrderCount eachOrder : eachProductCount) {
                    if (eachOrder.getProduct().equals(productDetail.getProductCode())) {

                        // 상태 요청 수량 변수에 해당 제품군의 총 요청 수량에 폐기 수량을 빼서 적용 후 탈출
                        statusCount = eachOrder.getOrderCount() - unExpectedDiscardProductCount;

                        break;
                    }
                }

                // 기존에 동일한 기기로 동일한 공급사가 동일한 고객사에 동일한 제품을 출고 시킨 RFID 스캔 이력이 존재하지 않으면 이력을 저장
                if (scanDataQueryData.checkSameCycleScanHistory(productDetail.getProductCode(), productDetail.getClientCode(), productDetail.getSupplierCode(), productDetail.getCycle(), "출고")) {
                    log.info("RfidScanHistory에 출고 이력을 저장하기 위한 로직 접근");
                    
                    // 만약 폐기 제품이 있을 경우 폐기 수량 저장 변수
                    int discardMount = discardHistoryQueryData.getDiscardHistory(productDetail.getSupplierCode(), productDetail.getProductCode());
                    // 직전 가장 최신 스캔 이력 조회
                    RfidScanHistory latestScanHistory = scanDataQueryData.getLatestRfidScanHistory(productDetail.getProductCode(), productDetail.getSupplierCode());
                    // 총 재고량
                    int totalRemainCount = supplierOrderQueryData.getTotalRemainCount(productDetail.getSupplierCode(), productDetail.getProductCode()) - discardMount;

                    // 만약 공급사 측에서 추가 물량을 주문했을 경우 재고 추가량 변수
                    int plusMount = 0;

                    // 유동 재고량을 위해 뺼셈 계산이 적용되어야할 유동 재고량 전용 폐기 수량
                    int minusCount = 0;

                    // 만약 해당 제품군의 총 폐기 수량과 현재 스캔한 제품 데이터들에 혹시라도 들어가있는 폐기 물품의 수량을 뺏을 때 0이 아닐 경우
                    if (discardMount - unExpectedDiscardProductCount != 0) {
                        // 거기에 현재 스캔한 제품 데이터들에 혹시라도 들어가있는 폐기 물품의 수량이 하나라도 존재할 경우 유동 재고량 계산을 위해
                        // 현재 빼야할 폐기 수량을 계산하여 minusCount 변수에 저장
                        if (unExpectedDiscardProductCount != 0) {
                            minusCount = discardMount - unExpectedDiscardProductCount + unExpectedDiscardProductCount;
                        }
                    }

                    // 첫 스캔 이력이 아닐 경우
                    if (latestScanHistory != null) {
                        log.info("기존 이력 존재 시 진입");

                        // 추가 재고량 추출 후 변수 저장
                        if (totalRemainCount - latestScanHistory.getTotalRemainQuantity() != 0) {
                            plusMount = totalRemainCount - latestScanHistory.getTotalRemainQuantity();
                        }

                        // RFID 기기 스캔 이력 정보
                        rfidScanHistory = RfidScanHistory.builder()
                                .deviceCode(deviceCode)
                                .rfidChipCode(productDetail.getRfidChipCode())
                                .productCode(productDetail.getProductCode())
                                .supplierCode(productDetail.getSupplierCode())
                                .clientCode(productDetail.getClientCode())
                                .status(productDetail.getStatus())
                                .statusCount(statusCount) //
                                .flowRemainQuantity(latestScanHistory.getFlowRemainQuantity() + plusMount - minusCount)
                                .noReturnQuantity(latestScanHistory.getNoReturnQuantity())
                                .totalRemainQuantity(totalRemainCount)
                                .cycle(productDetail.getCycle())
                                .latestReadingAt(LocalDateTime.now())
                                .productDetailHistories(rfidHistoryMappingProductDetailHistorties)
                                .build();

                    } else { // 첫 스캔 이력으로 들어오는 경우
                        log.info("기존 이력이 존재하지 않는 첫 이력일 경우 진입");

                        // RFID 기기 스캔 이력 정보
                        rfidScanHistory = RfidScanHistory.builder()
                                .deviceCode(deviceCode)
                                .rfidChipCode(productDetail.getRfidChipCode())
                                .productCode(productDetail.getProductCode())
                                .supplierCode(productDetail.getSupplierCode())
                                .clientCode(productDetail.getClientCode())
                                .status(productDetail.getStatus())
                                .statusCount(statusCount)
                                .flowRemainQuantity(totalRemainCount)
                                .noReturnQuantity(0)
                                .totalRemainQuantity(totalRemainCount)
                                .cycle(productDetail.getCycle())
                                .latestReadingAt(LocalDateTime.now())
                                .productDetailHistories(rfidHistoryMappingProductDetailHistorties)
                                .build();
                    }

                    // 저장
                    rfidScanHistoryRepository.save(rfidScanHistory);
                    rfidScanHistoryList.add(rfidScanHistory);

                    // 확인용 반환 DTO 객체에 저장
                    responseDto = RfidScanDataOutResponseDto.builder()
                            .machineId(rfidScanHistory.getDeviceCode())
                            .tag(tag)
                            .selectClientCode(rfidScanHistory.getClientCode())
                            .supplierCode(rfidScanHistory.getSupplierCode())
                            .status(rfidScanHistory.getStatus())
                            .statusCount(rfidScanHistory.getStatusCount())
                            .flowRemainQuantity(rfidScanHistory.getFlowRemainQuantity())
                            .noReturnQuantity(rfidScanHistory.getNoReturnQuantity())
                            .totalRemainQuantity(rfidScanHistory.getTotalRemainQuantity())
                            .latestReadingAt(rfidScanHistory.getLatestReadingAt().toString())
                            .productDetails(productDetailResponseDtos)
                            .build();
                } else {
                    log.info("RfidScanHistory에 이미 동일한 내용의 출고 이력이 존재할 경우 접근");

                    // 잘못된 상태 처리로 인해 바로 다시 재차 스캔하여 상태 처리 했을 경우를 확인하기 위한 이력 조회
                    if (scanDataQueryData.asapScanForPrevHistory(deviceCode, productDetail.getProductCode(), productDetail.getClientCode(), productDetail.getSupplierCode(), productDetail.getCycle(), statusCount)) {
                        log.info("즉시 바로 재차 출고 처리");
                    }
                }

                log.info("대조시킬 RFID 스캔 이력 저장 리스트 확인 : {}", rfidScanHistoryList.size());

                // 기존에 이미 동일한 내용의 ProductDetailHistory 데이터가 존재하는 지 확인 후, 존재하지 않을 경우 해당 데이터 생성
                if (productDetailHistoryQueryData.checkPreviewHistory(
                        receiveProductCodes.get(i).getRfidChipCode(),
                        receiveProductCodes.get(i).getProductSerialCode(),
                        receiveProductCodes.get(i).getProductCode(),
                        supplierCode, clientCode, "출고", productDetail.getCycle())) {

                    for (RfidScanHistory eachScanHistory : rfidScanHistoryList) {

                        log.info("대조시킬 RFID 리스트에 저장된 제품 코드 확인 : {}", eachScanHistory.getProductCode());
                        log.info("이력 id : {}", eachScanHistory.getRfidScanhistoryId());

                        if (eachScanHistory.getProductCode().equals(receiveProductCodes.get(i).getProductCode())) {
                            // 각 제품 스캔 상세 이력
                            ProductDetailHistory productDetailHistory = ProductDetailHistory.builder()
                                    .rfidChipCode(receiveProductCodes.get(i).getRfidChipCode())
                                    .productSerialCode(receiveProductCodes.get(i).getProductSerialCode())
                                    .productCode(receiveProductCodes.get(i).getProductCode())
                                    .supplierCode(supplierCode)
                                    .clientCode(clientCode)
                                    .status("출고")
                                    .cycle(productDetail.getCycle())
                                    .latestReadingAt(LocalDateTime.now())
                                    .rfidScanHistory(eachScanHistory)
                                    .build();

                            // 저장
                            productDetailHistoryRepository.save(productDetailHistory);

                            log.info("productDetailHistory이 저장됨을 확인 : {}", productDetailHistory.getProductDetailHistoryId());
                        }

                    }
                }
            }
        }
    }

    Thread.sleep(3000);

    //return new ResponseEntity<>(new ResponseBody(StatusCode.OK, responseDto), HttpStatus.OK);
    return new AsyncResult<>(responseDto).completable();
}

 

안드로이드 어플에서 약 1500개 가량의 임시 스캔  데이터를 한 번에 스캔하고 출고 처리를 백엔드 서버에 요청했을 때 현재 위의 기존 로직을 수행하게 되면 빨라도 30분 정도, 혹은 1시간 정도의 시간이 소요되어 매우 당황스러웠습니다.

 

또한 과도하게 서비스 요청 시간이 소요되어 배포하여 실행 중인 백엔드 서버 측에서도 Session aborted 에러가 발생되면서 연결이 끊어지는 현상까지 발생되었습니다.

 

RFID를 통해 실시간으로 제품들을 스캔하여 상태 처리를 진행해야 했으므로 이는 매우 크리티컬한 이슈라고 판단되어서 여러가지 방법을 통해 시간 소요를 줄이는 것이 당연하다는 생각이 들었습니다.

 


 

방법 1. 기존 작성 코드 재차 확인 및 개선점 파악

기존 문제 로직

List<SendProductCode> receiveProductCodes = sendOutDatas.getProductCodes(); // 스캔한 제품 데이터들의 제품 분류 코드와 각 제품의 시리얼 코드 리스트
List<SendOrderCount> eachProductCount = sendOutDatas.getEachProductCount(); // 각 제품의 출고 제품 수량

for (int i = 0; i < receiveProductCodes.size(); i++) {

.... 다른 로직 코드

    // 예기치 않은 폐기 제품 수 (RfidScanHIstory의 flowRemainCount를 계산하기 위한 용도)
    int unExpectedDiscardProductCount = 0;

    // 스캔하여 들어온 제품 데이터들을 하나씩 조회
    for (SendProductCode eachProductCode : receiveProductCodes) {
        // 들어온 제품 데이터의 필터링 코드에 CCA2310가 있어야 하고, 현재 조회한 제품의 코드가 동시에 작업 중인 ProductDetail의 제품 코드와 일치할 경우 진입
        if (eachProductCode.getFilteringCode().contains("CCA2310") && eachProductCode.getProductCode().equals(productDetail.getProductCode())) {
            // 이미 스캔한 제품 데이터 중 특정 제품이 폐기 이력에 존재한다면 계산하기 위한 unExpectedDiscardProductCount 변수에 +1
            if (discardHistoryQueryData.checkProductDiscard(eachProductCode.getProductCode(), eachProductCode.getProductSerialCode())) {
                 unExpectedDiscardProductCount += 1;
            }
        }
    }
 
 .... 다른 로직 코드
 
}

기존 작성한 백엔드 코드 로직을 보면 위와 같이 작성을 해놓았는데, receiveProductCodes가 본격적으로 스캔하여 들어온 1500개 가량의 제품 데이터들입니다.

이를 for문으로 하나씩 조회하여 상세한 정보들을 특정 로직을 통해 작업을 수행하고 데이터베이스에 저장 및 상태 처리를 수행하게 되는데 이 때, 가장 문제 되는 것이 바로 위의 2차 for문이였습니다.

 

왜냐, 1500개 가량의 데이터를 하나씩 조회하는데 1500개 중 1개의 데이터를 조회할 때마다 다시 1500 개의 데이터를 모두 대조하여 상태를 처리하려고 했기 때문에 1개의 데이터 당 다시 1500개의 데이터를 비교 처리 해주도록 되어있기 때문에 엄청난 시간 소요가 되었던 것입니다.

 

또한, 해당 작업은 들어온 제품 데이터가 기존에 폐기된 이력이 있는지 조회해보는 로직이기 때문에 쿼리를 통해 데이터베이스에 접근하는 것 또한 다시 1500번 수행하게 되어 안그래도 과도한 시간 소요가 더욱 과도해진 것입니다.

 

 

 

변경한 로직

List<SendProductCode> receiveProductCodes = sendOutDatas.getProductCodes(); // 스캔한 제품 데이터들의 제품 분류 코드와 각 제품의 시리얼 코드 리스트
List<SendOrderCount> eachProductCount = sendOutDatas.getEachProductCount(); // 각 제품의 출고 제품 수량

// 예기치 않은 폐기 제품 수 (RfidScanHIstory의 flowRemainCount를 계산하기 위한 용도)
AtomicInteger unExpectedDiscardProductCount = new AtomicInteger();

for (SendOrderCount countInfo : eachProductCount) {
	if (discardHistoryQueryData.firstCheckDiscardHistory(countInfo.getProduct())) {
		receiveProductCodes.forEach(notAllowedDiscardProduct -> {
			if (discardHistoryQueryData.checkProductDiscard(notAllowedDiscardProduct.getProductCode(), notAllowedDiscardProduct.getProductSerialCode())) {
				unExpectedDiscardProductCount.addAndGet(1);
			}
		});
	}
}

for (int i = 0; i < receiveProductCodes.size(); i++) {

.... 다른 로직 코드
 
}

따라서, 2차 for문 로직을 1차 for문 바깥으로 빼내고 사전 작업으로 폐기 이력 조회 로직 수행 및 변수 저장을 선행시켜주었습니다.

뿐만 아니라 기존에는 폐기 이력 유무에 상관없이 무조건 폐기 이력을 조회하고 확인하는 작업을 거쳤는데 이번에는 해당

ProductCode가 폐기된 이력 테이블에 존재하지 않는다면 폐기 이력 조회를 수행하지 않도록 하여 시간 소요를 더욱 단축시킬 수 있었습니다.

 

 


 

방법 2. 비동기 처리

기존에는 별도의 설정을 적용하지 않은 api 로서 동기 방식으로 하나의 요청에 반드시 응답이 와야지 다음 작업이 수행되기 때문에 이를 비동기 처리하면 더욱 빠른 작업을 수행할 수 있지 않을까 하여 비동기를 적용시키기로 마음 먹었습니다.

 

AsyncConfig


@EnableAsync
@Configuration
public class AsyncConfig {
    // Async용 설정 파일
    // 스레드를 관리하기 위한 스레드 풀 설정 파일

    /* 스레드 풀 설정.
     * 이 스레드 설정 메소드를 Service의 멀티스레드가 적용될 동작메소드에 매핑시켜주면
     * 해당 Service의 메소드는 비동기 방식으로 멀티 스레드가 동작된다.
     */
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10); // 기본 스레드 수
        taskExecutor.setMaxPoolSize(100); // 최대 스레드 수
        taskExecutor.setQueueCapacity(500); // Queue 사이즈
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }

}

비동기 처리에는 다양한 방법이 존재하지만 저는 Async를 통해 멀티쓰레드 환경으로 비동기 처리를 수행하고자 하였습니다.

이를 위해 AsyncConfig 설정 클래스 파일을 만들고, 쓰레드에 대한 설정을 진행해주었습니다.

 

실행 쓰레드를 관리해주는 ThreadPoolTaskExecutor 를 호출하여 setCorePoolSize(10) 을 통해 기본 스레드 수를, setMaxPoolSize(100) 을 통해 최대 스레드 수, setQueueCapacity(500) 을 통해 실행 우선순위를 정해주는 Queue 사이즈를 지정, setThreadNamePrefix 를 통해 실행 스레드의 이름을 정해주었습니다.

여기서 Bean 어노테이션을 통해 빈 객체로 지정해줄 때 name 옵션을 통해 이름을 지정해주었습니다.

이 name 옵션을 통해 이후 비동기를 적용할 함수와 매핑하여 비동기 처리가 가능하도록 설정해주는 것입니다.

 

 

 

적용 함수

@Async("threadPoolTaskExecutor")
@Transactional
public CompletableFuture<RfidScanDataOutResponseDto> sendOutData(RfidScanDataOutRequestDto sendOutDatas) throws InterruptedException {
    log.info("제품 출고 처리 service");
    .... 기타 로직
    
    return new AsyncResult<>(responseDto).completable();
}

적용하고자 하는 함수에 Async 어노테이션을 붙여주고 안에 빈 객체로 설정한 이름을 넣어주면 적용되게 됩니다.

그리고 Async를 적용시켰으므로 해당 함수의 반환값을 Async와 관련된 CompletableFuture로 지정해주었습니다.

 

이 CompletableFuture 에 대한 내용은 추후에 정리해보도록 하겠습니다.

 

 

 

이로써 위의 두 가지 개선 방법을 통해 기존에 몇 십분 혹은 1시간 정도로 소요되던 시간을 최대 3분 정도까지로 단축시킬 수 있었습니다.

사실, 문제점 중 가장 큰 부분은 기존에 작성했던 코드 로직이 비효율적으로 구성되어있던 것이 가장 큰 문제였습니다.

 

728x90
반응형
LIST