포스트

모든 쿼리에 스톱워치 달기: AOP 횡단 관심사

AOP로 메서드·쿼리 실행시간을 횡단 측정하는 인터셉터의 동작 원리와 프록시 한계를 다룬다.

모든 쿼리에 스톱워치 달기: AOP 횡단 관심사

측정 코드를 모든 메서드에 넣을 순 없다

“각 리포지토리 메서드가 몇 ms 걸렸나”를 알려고 메서드마다 System.nanoTime()을 넣으면, 측정 코드가 비즈니스 로직에 들러붙는다. 실행시간 측정은 전형적인 횡단 관심사이고, AOP가 이를 한 곳으로 분리한다.

동작 — 프록시와 around 어드바이스

스프링 AOP는 대상 빈을 프록시로 감싸고, 포인트컷에 매칭되는 호출을 가로챈다. @Around 어드바이스는 실제 호출 전후를 감싸 시간을 잰다.

1
2
3
4
5
6
7
8
9
@Around("execution(* com.example..repository..*(..))")
public Object timed(ProceedingJoinPoint pjp) throws Throwable {
    long t = System.nanoTime();
    try { return pjp.proceed(); }
    finally {
        long ms = (System.nanoTime() - t) / 1_000_000;
        MDC.put("queryMs", String.valueOf(ms));   // 구조화 로그에 실림
    }
}
sequenceDiagram
    Caller->>Proxy: findById()
    Proxy->>Proxy: start timer
    Proxy->>Target: proceed()
    Target-->>Proxy: result
    Proxy->>Proxy: log elapsed
    Proxy-->>Caller: result

프록시의 한계 (가장 중요한 함정)

  • self-invocation: 같은 빈 안에서 this.method()로 부르면 프록시를 거치지 않아 측정이 안 된다. 슬로우 쿼리 글(DB측 로그)과 달리 AOP는 “프록시 경유 호출”만 본다.
  • 포인트컷 과대 매칭: execution(* *(..))처럼 넓게 잡으면 모든 메서드에 프록시 오버헤드가 붙는다. 패키지/애너테이션으로 좁힌다.

핵심 요약

AOP 타이밍은 측정 로직을 비즈니스에서 떼어내는 깔끔한 방법이지만, 프록시 경유 호출만 잡는다는 전제를 잊으면 self-invocation 메서드가 측정에서 통째로 빠진다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.