惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
量子位
T
Tailwind CSS Blog
Vercel News
Vercel News
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
U
Unit 42
Engineering at Meta
Engineering at Meta
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
D
Docker
博客园_首页
P
Proofpoint News Feed
月光博客
月光博客
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Martin Fowler
Martin Fowler
腾讯CDC
N
Netflix TechBlog - Medium
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
단계별 성능 측정
GyeongSeon · 2026-06-17 · via DEV Community

GyeongSeon

단계별 성능 측정

하나의 API 요청 안에서 각 기능별 시간을 누적하다가, 요청이 끝날 때 한 번에 로그로 출력하는 구조네요.

ThreadLocal로 컨텍스트를 유지하는 방식이 가장 깔끔합니다.


전체 구조

Request → Filter(시작) → Controller → Service → Repository → Filter(종료+로그출력)
                              ↓           ↓          ↓
                         AOP가 각 단계 시간을 PerformanceContext에 누적


1. PerformanceContext — ThreadLocal로 요청당 컨텍스트 유지

@Component
public class PerformanceContext {

    private static final ThreadLocal<List<StepRecord>> steps = 
        ThreadLocal.withInitial(ArrayList::new);
    private static final ThreadLocal<Long> requestStart = new ThreadLocal<>();

    public void startRequest() {
        steps.get().clear();             // 이전 요청 데이터 초기화
        requestStart.set(System.nanoTime());
    }

    public void record(String stepName, long elapsedMs) {
        steps.get().add(new StepRecord(stepName, elapsedMs));
    }

    public String buildSummary() {
        long totalMs = (System.nanoTime() - requestStart.get()) / 1_000_000;
        StringBuilder sb = new StringBuilder("\n==== API Performance Summary ====\n");

        for (StepRecord step : steps.get()) {
            sb.append(String.format("  %-40s %5dms\n", step.name(), step.elapsedMs()));
        }

        sb.append("---------------------------------\n");
        sb.append(String.format("  %-40s %5dms\n", "TOTAL", totalMs));
        sb.append("=================================");
        return sb.toString();
    }

    public void clear() {
        steps.remove();
        requestStart.remove();
    }

    public record StepRecord(String name, long elapsedMs) {}
}


2. @TrackTime — 측정할 메서드에 붙이는 어노테이션

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
    String value() default "";   // 로그에 표시할 이름 (비어있으면 메서드명 사용)
}


3. PerformanceAspect — AOP로 시간 측정 후 Context에 기록

@Aspect
@Component
@RequiredArgsConstructor
public class PerformanceAspect {

    private final PerformanceContext performanceContext;

    @Around("@annotation(trackTime)")
    public Object track(ProceedingJoinPoint joinPoint, TrackTime trackTime) throws Throwable {
        String stepName = trackTime.value().isBlank()
            ? joinPoint.getSignature().toShortString()
            : trackTime.value();

        long start = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            long elapsed = (System.nanoTime() - start) / 1_000_000;
            performanceContext.record(stepName, elapsed);
        }
    }
}


4. PerformanceFilter — 요청 시작/종료 시점 제어

@Component
@RequiredArgsConstructor
public class PerformanceFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(PerformanceFilter.class);
    private final PerformanceContext performanceContext;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        performanceContext.startRequest();
        try {
            filterChain.doFilter(request, response);
        } finally {
            log.info("[{} {}]{}", 
                request.getMethod(), 
                request.getRequestURI(),
                performanceContext.buildSummary());
            performanceContext.clear();   // 반드시 ThreadLocal 정리
        }
    }
}


5. 실제 사용 — 각 레이어에 어노테이션만 붙이면 끝

@RestController
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping("/api/orders")
    public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
        return ResponseEntity.ok(orderService.createOrder(request));
    }
}

@Service
@RequiredArgsConstructor
public class OrderService {

    private final UserService userService;
    private final ProductService productService;
    private final OrderRepository orderRepository;

    @TrackTime("1. 주문 처리 (Service)")
    public OrderResponse createOrder(OrderRequest request) {
        User user = userService.validateUser(request.getUserId());
        Product product = productService.checkStock(request.getProductId());
        return orderRepository.save(user, product);
    }
}

@Service
public class UserService {

    @TrackTime("2. 유저 검증 (UserService)")
    public User validateUser(Long userId) {
        // DB 조회, 권한 체크 등
    }
}

@Service
public class ProductService {

    @TrackTime("3. 재고 확인 (ProductService)")
    public Product checkStock(Long productId) {
        // 재고 조회
    }
}

@Repository
public class OrderRepository {

    @TrackTime("4. 주문 저장 (Repository)")
    public OrderResponse save(User user, Product product) {
        // INSERT 쿼리
    }
}


출력 결과

INFO  [POST /api/orders]
==== API Performance Summary ====
  2. 유저 검증 (UserService)                   12ms
  3. 재고 확인 (ProductService)                 8ms
  4. 주문 저장 (Repository)                    23ms
  1. 주문 처리 (Service)                       48ms   ← 하위 포함한 전체
---------------------------------
  TOTAL                                        51ms   ← Filter 기준 전체
=================================


핵심 포인트 정리

역할 클래스 설명
컨텍스트 저장 PerformanceContext ThreadLocal로 요청별 격리
시간 측정 PerformanceAspect AOP로 비침투적 측정
시작/종료 제어 PerformanceFilter 로그 출력 + ThreadLocal 정리
측정 대상 지정 @TrackTime 어노테이션만 붙이면 자동 수집

ThreadLocal을 쓰므로 멀티스레드 환경에서도 요청끼리 간섭 없이 각자의 데이터를 독립적으로 유지합니다. finally에서 clear()를 반드시 호출해야 스레드 풀 재사용 시 데이터가 오염되지 않습니다.