[InConcert] 스크래핑 비동기 처리(2)

2025. 1. 15. 14:29·Projects

앞 내용은 [InConcert] 스크래핑 비동기 처리(1) ☜ 여기서 확인

 

1. AOP를 활용한 성능 측정 및 최적화

비동기 및 병렬 처리 적용 후, 실제로 성능이 얼마나 개선되었는지 확인하기 위해 `AOP(Aspect-Oriented Programming)`를 활용하여 실행 시간을 측정했다.

AOP를 적용하면 스크래핑 로직의 실행 시간을 자동으로 로깅할 수 있어, 성능 개선 효과를 수치적으로 분석할 수 있다.

 


2. AOP를 활용한 실행 시간 측정

실행 시간 측정 애노테이션 생성

먼저, 실행 시간을 측정할 메서드에 적용할 애노테이션을 정의했다.

이 애노테이션을 특정 메서드에 추가하면, AOP가 실행 시간을 자동으로 기록한다.

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

 

실행 시간 로깅 AOP 구현

이제 `@LogExecutionTime` 애노테이션이 적용된 메서드의 실행 시간을 측정하는 AOP 클래스를 만들었다.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ExecutionTimeAspect {
    @Around("@annotation(com.inconcert.common.annotation.LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();                    // 시작 시간 기록
        Object result = joinPoint.proceed();                            // 메서드 실행
        long endTime = System.currentTimeMillis();                      // 종료 시간 기록
        double executionTimeInSeconds = (endTime - startTime) / 1000.0; // ms를 초 단위로 변환

        System.out.println("스크래핑 실행 시간: " + executionTimeInSeconds + "초");
        return result;
    }
}

 

`@LogExecutionTime` 이 붙은 메서드가 실행될 때마다, AOP가 실행 시간을 측정하고 로그로 남긴다.

 


3. 실행 시간 비교

▶ 기존 코드

기존에는 `crawlIfNecessary()` 메서드가 동기적으로 실행되어, 스크래핑이 끝날 때까지 전체 프로세스가 블로킹되는 문제가 있었다. 

@Service
@RequiredArgsConstructor
public class CrawlingService {
    private final InfoService infoService;
    private final InfoRepository infoRepository;
    private final PerformanceRepository performanceRepository;

    @Transactional
    @LogExecutionTime  // 실행 시간 측정 적용
    public void crawlIfNecessary() {
        Performance lastCrawl = performanceRepository.findTopByOrderByIdDesc();
        LocalDateTime now = LocalDateTime.now();

        // 크롤링이 되어있지 않은 상태
        if (lastCrawl == null) {
            performCrawling();
        }
        // 마지막으로 크롤링한 지 24시간이 지났을 때 다시 크롤링
        else if (ChronoUnit.HOURS.between(lastCrawl.getUpdatedAt(), now) >= 24) {
            performanceRepository.deleteAll();    // 이전 크롤링 지우기
            infoRepository.afterCrawling();
            performCrawling();
        }
    }

    @Transactional
    protected void performCrawling() {
        for (int type = 1; type <= 4; type++) {
            infoService.crawlAndSavePosts(String.valueOf(type));
        }
    }
}

 

▶ 실행 결과

위 사진과 같이 10회 정도 실행한 결과 `평균 146초`가 소요됐다.

 

▶ 개선 코드

비동기 처리와 병렬 실행을 적용하여, 스크래핑 시간이 대폭 단축되었다.

@Service
public class ScrapingLoggingService {
    @Async
    @LogExecutionTime
    public void measureScrapingPerformance(Runnable task) {
        task.run();
    }
}
@Service
@RequiredArgsConstructor
@Slf4j
public class PerformanceService {
    private final PerformanceRepository performanceRepository;
    private final InfoRepository infoRepository;
    private final UserService userService;
    private final ScrapingLoggingService scrapingLoggingService;

    private volatile boolean isCrawling = false;
    private final Object crawlingLock = new Object();

    public boolean isCrawling() {
        return isCrawling;
    }

    @Async
    public void startCrawlingAsync() {
        // 중복 실행 방지
        synchronized (crawlingLock) {
            if (isCrawling) {
                log.info("스크래핑이 이미 진행 중입니다. 중복 실행 방지.");
                return;
            }
            isCrawling = true;
        }

        CompletableFuture.runAsync(() -> {
            try {
                List<CompletableFuture<Void>> futures = new ArrayList<>();
                for (int type = 1; type <= 4; type++) {
                    int finalType = type;
                    futures.add(CompletableFuture.runAsync(() ->
                            scrapingLoggingService.measureScrapingPerformance(() ->
                                    crawlPerformances(String.valueOf(finalType)))));
                }
                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            } finally {
                synchronized (crawlingLock) {
                    isCrawling = false;
                }
            }
        });
    }
}

 

위 사진과 같이 10회 정도 실행한 결과 `평균 64초`가 소요됐다.

즉 `57%의 성능`을 향상시켰다. 

 


4. 결론

이번 AOP 적용을 통해 비즈니스 로직과 성능 측정 로직을 분리함으로써
코드의 유지보수성이 향상되는 효과를 직접 경험할 수 있었다.
또한, 성능 분석 결과 스크래핑 시간이 평균 146초에서 64초로 단축되며
약 57%의 성능 향상을 달성할 수 있었다.

 

'Projects' 카테고리의 다른 글

[Poppy] Redisson 기반 분산 락을 활용한 예약 시스템 동시성 제어  (0) 2025.02.26
[InConcert] SSE+Redis를 활용한 실시간 알림  (0) 2025.01.22
[InConcert] LazyInitializationException 해결  (0) 2025.01.19
[InConcert] 스크래핑 비동기 처리(1)  (0) 2025.01.10
'Projects' 카테고리의 다른 글
  • [Poppy] Redisson 기반 분산 락을 활용한 예약 시스템 동시성 제어
  • [InConcert] SSE+Redis를 활용한 실시간 알림
  • [InConcert] LazyInitializationException 해결
  • [InConcert] 스크래핑 비동기 처리(1)
oniwon
oniwon
  • oniwon
    눙눙의 개발일지
    oniwon
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Java
      • Spring
      • CS
      • Infra
      • Projects
      • F-Lab
      • Airflow
  • 인기 글

  • 블로그 메뉴

    • 홈
    • 개인정보처리방침 (Privacy Policy)
    • 블로그 소개 (About)
  • 최근 글

  • 태그

    inconcert
    Java
    F-Lab
    Airflow
    자바백엔드
    에프랩
    자바 다형성
    커넥터스2385
    Flab커넥터스
    비동기 처리
    Flab 멘토링
    tls
    에프랩멘토링
    SSE
    Flab추천인코드
    redis
    https
    병렬 처리
    자바 내부 동작
    dns
    동시성
    FLAB
    Flab 3주차 후기
    DAG
    자바면접
    Flab멘토링
    Flab 추천인코드
    Flab후기
    네트워크
    JVM
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.1
oniwon
[InConcert] 스크래핑 비동기 처리(2)
상단으로

티스토리툴바