앞 내용은 [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 |