부하테스트 개념 및 실습

LiiNi·2025년 9월 1일

부하테스트

서비스의 한계를 실험해봐야 추후 프리즈 되는 상황을 대처한 개발을 가능

부하테스트

  • Load & Stress 테스트
    • load: 시스템이 예상하는 일반 트래픽을 처리하는지
    • stress: 시스템에 고의로 점진적으로 과부하를 가하여 한계점을 찾고 장애상황에서 안정적 복구(회복)되는지 테스트
  • spike 테스트
    • spike : 순간적인 과부하를 가하여 그 회복력 테스트
  • Soak 테스트
    • 메모리 누수나 리소스 고갈같은 장기적 성능문제를 발견

지표

  • 응답시간: 사용자 요청 보내고 받는데 까지의 전체 시간
    • 응답시간 = 지연시간 + 연산시간(처리)
  • 지연시간: 데이터가 전송되는 데 걸리는 시간 또는 요청이 처리되길 기다리는 시간을 의미
  • 처리량(Throughput): 초당 최대요청 처리량
    • TPS: 총 트랜젝션수 / 총 소요시간
    • RPS: 총 요청수 / 총 소요시간
  • MTBF(Mean time betrewwn 실패): 얼마나 오랫동안 문제없이 동작하였는가
  • MTTR(Mean time to recovery) : 문제가 생겼을때 얼마나 빨리 작동하는가제가 생겼을때 얼마나 빨리 작동하는가

부하테스트 모니터링

프로메테우스

  • 메트릭을 수집하는 모니터링 툴
  • 서버의 상태를 계속 감시하고 CPU사용량, 메모리, 네트워크 트래픽 등과 같은 데이터를 기록하는 수집하고 저장

그라파나

  • 프로메테우스의 숫자 데이터(프로메테우스 외에도 엘라스틱 서치등)를 시각화 하는 툴

K6

  • 시스템에 부하를 발생시켜 성능을 테스트하는 부하 테스트 툴
  • js기반으로 테스트 스크립트를 작성하여 자동화 가능

실습

환경

  • 두개의 ec2에 프로메테우스, 그라파나, k6 를 한데 실행.
    • 1st
      • t2-micro
      • 인터넷에서 http 허용 체크
      • 원래 각각마다 ec2를 두는 게 현업에 맞지만 자원상 한계
      • 키페어 없이
      • 보안그룹 8080 허용
      • 보안그룹 8081 허용: spring actuator를 위함
    • 나머진 그대로 하여 인스턴스 하나 생성
    • 2nd 모니터링 ec2
    • t2-micro
      • 보안그룹
        • 3000 : 그라파나 위함
        • 9090 : 프로메테우스가 이걸 씀
        • 5665 : 그라파나에서 웹모니터링을 제공은 하나, k6 자체로도 대시보드가 있어서 그걸 확인하기 위함

인스턴스 쉘 진입 후

  1. 두 ec2모두 적용
export API_SERVER_IP="[본인의_API_SERVER_퍼블릭_IP]"
export MONITORING_SERVER_IP="[본인의_MONITORING_SERVER_퍼블릭_IP]"
  1. 편집기로 setting.sh로 다음내용 생성
#!/bin/bash

set -e

echo "=== Spring Boot API Server Setup (병렬 처리) ==="

# 기본 패키지 설치
sudo apt update -y
sudo apt install -y openjdk-17-jdk maven git curl

# 프로젝트 구조 생성
mkdir -p ~/loadtest-app && cd ~/loadtest-app
mkdir -p loadtest-api/src/main/java/com/loadtest
mkdir -p loadtest-api/src/main/resources
cd loadtest-api

# pom.xml 생성
cat > pom.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <groupId>com.loadtest</groupId>
    <artifactId>loadtest-api</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
EOF

# Application 클래스 생성
cat > src/main/java/com/loadtest/LoadTestApplication.java << 'EOF'
package com.loadtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LoadTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(LoadTestApplication.class, args);
    }
}
EOF

# Controller 생성 (병렬 처리 + 병목 시나리오)
cat > src/main/java/com/loadtest/LoadTestController.java << 'EOF'
package com.loadtest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

@RestController
public class LoadTestController {

    @Autowired
    private RestTemplate restTemplate;
    
    @Autowired
    private Executor taskExecutor;

    // API 1 - 병목 지점 (50ms)
    @GetMapping("/api/1")
    public Map<String, Object> api1() throws InterruptedException {
        Thread.sleep(50);  // 50ms - 심각한 병목
        Map<String, Object> response = new HashMap<>();
        response.put("endpoint", "api1");
        response.put("processingTime", "50ms");
        response.put("timestamp", System.currentTimeMillis());
        response.put("role", "심각한 병목 - 50ms");
        return response;
    }

    // API 2 - 중간 성능 (2ms)
    @GetMapping("/api/2")
    public Map<String, Object> api2() throws InterruptedException {
        Thread.sleep(2);  // 2ms
        Map<String, Object> response = new HashMap<>();
        response.put("endpoint", "api2");
        response.put("processingTime", "2ms");
        response.put("timestamp", System.currentTimeMillis());
        return response;
    }

    // API 3 - 빠른 성능 (1ms)
    @GetMapping("/api/3")
    public Map<String, Object> api3() throws InterruptedException {
        Thread.sleep(1);  // 1ms
        Map<String, Object> response = new HashMap<>();
        response.put("endpoint", "api3");
        response.put("processingTime", "1ms");
        response.put("timestamp", System.currentTimeMillis());
        return response;
    }

    // 비즈니스 플로우 - 병렬 호출
    @GetMapping("/api/business-flow")
    public Map<String, Object> businessFlow() {
        long startTime = System.currentTimeMillis();
        String baseUrl = "http://localhost:8080";
        
        try {
            // 3개 API를 병렬로 동시 호출
            CompletableFuture<Map> future1 = CompletableFuture.supplyAsync(() -> 
                restTemplate.getForObject(baseUrl + "/api/1", Map.class), taskExecutor);
            CompletableFuture<Map> future2 = CompletableFuture.supplyAsync(() -> 
                restTemplate.getForObject(baseUrl + "/api/2", Map.class), taskExecutor);
            CompletableFuture<Map> future3 = CompletableFuture.supplyAsync(() -> 
                restTemplate.getForObject(baseUrl + "/api/3", Map.class), taskExecutor);
            
            // 가장 오래 걸리는 API까지 대기 (api/1의 50ms)
            CompletableFuture.allOf(future1, future2, future3).join();
            
        } catch (Exception e) {
            System.err.println("Parallel API call failed: " + e.getMessage());
        }
        
        long endTime = System.currentTimeMillis();
        Map<String, Object> response = new HashMap<>();
        response.put("endpoint", "business-flow");
        response.put("executionType", "병렬 처리");
        response.put("description", "api/1 || api/2 || api/3 동시 실행");
        response.put("totalProcessingTime", (endTime - startTime) + "ms");
        response.put("timestamp", endTime);
        response.put("bottleneck", "api/1 (50ms) - 심각한 성능 저하");
        response.put("theoreticalMaxTPS", "약 20 TPS (1000ms / 50ms)");
        response.put("improvementPotential", "api/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)");
        return response;
    }

    @GetMapping("/health")
    public Map<String, String> health() {
        Map<String, String> response = new HashMap<>();
        response.put("status", "UP");
        return response;
    }
}

@Configuration
@EnableAsync
class RestConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("parallel-api-");
        executor.initialize();
        return executor;
    }
}
EOF

# application.yml 설정
cat > src/main/resources/application.yml << 'EOF'
server:
  port: 8080
  tomcat:
    threads:
      max: 300
      min-spare: 20

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
  endpoint:
    health:
      show-details: always
    metrics:
      enabled: true
    prometheus:
      enabled: true
  server:
    port: 8081

logging:
  level:
    com.loadtest: INFO
    org.springframework: WARN
    org.springframework.web: DEBUG
EOF

# 빌드 및 실행
echo "=== Building Application ==="
mvn clean package -DskipTests

if [ $? -eq 0 ]; then
    echo "Build SUCCESS"
    
    nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar > app.log 2>&1 &
    echo $! > app.pid
    
    echo "=== Waiting for Application Startup ==="
    sleep 15
    
    echo "=== Testing Endpoints ==="
    if curl -s http://localhost:8080/health | grep -q "UP"; then
        echo "Health Check OK"
        
        # 각 API 개별 테스트
        echo "개별 API 테스트:"
        echo -n "api/1: "; curl -s http://localhost:8080/api/1 | jq -r '.processingTime // "OK"'
        echo -n "api/2: "; curl -s http://localhost:8080/api/2 | jq -r '.processingTime // "OK"'
        echo -n "api/3: "; curl -s http://localhost:8080/api/3 | jq -r '.processingTime // "OK"'
        
        echo ""
        echo "Spring Boot API Server Ready!"
        echo ""
        echo "병렬 처리 시나리오: 가장 느린 api/1이 전체 성능 결정"
    else
        echo "Application failed to start"
        cat app.log
        exit 1
    fi
else
    echo "Build FAILED"
    exit 1
fi

# 성능 개선 시뮬레이션 스크립트 생성
cat > improve-api1.sh << 'EOF'
#!/bin/bash
echo "=== API/1 성능 개선 시뮬레이션 ==="

./stop.sh

# api/1의 성능을 50ms에서 2ms로 개선
sed -i 's/Thread.sleep(50);  \/\/ 50ms - 심각한 병목/Thread.sleep(2);  \/\/ 2ms - 개선완료/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"processingTime", "50ms"/"processingTime", "2ms"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"role", "심각한 병목 - 50ms"/"role", "개선완료 - 최적화됨"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"bottleneck", "api\/1 (50ms) - 심각한 성능 저하"/"bottleneck", "없음 - 모든 API 최적화됨"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"theoreticalMaxTPS", "약 20 TPS (1000ms \/ 50ms)"/"theoreticalMaxTPS", "약 500 TPS (1000ms \/ 2ms)"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"improvementPotential", "api\/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)"/"improvement", "api\/1 최적화 완료 - 25배 성능 향상 달성"/g' src/main/java/com/loadtest/LoadTestController.java

mvn clean package -DskipTests
nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar > app.log 2>&1 &
echo $! > app.pid

sleep 10
EOF

# 관리 스크립트들 생성
cat > stop.sh << 'EOF'
#!/bin/bash
if [ -f app.pid ]; then
    sudo kill $(cat app.pid) 2>/dev/null
    rm app.pid
    echo "Application stopped"
else
    echo "No application running"
fi
EOF

cat > status.sh << 'EOF'
#!/bin/bash
echo "=== Application Status ==="
if [ -f app.pid ]; then
    PID=$(cat app.pid)
    if ps -p $PID > /dev/null; then
        echo "Application running (PID: $PID)"
        echo "Health: $(curl -s http://localhost:8080/health)"
    else
        echo "PID file exists but process not running"
    fi
else
    echo "Application not running"
fi
EOF

chmod +x stop.sh improve-api1.sh status.sh

echo ""
echo "Management Commands:"
echo "- Status: ./status.sh"
echo "- Stop: ./stop.sh"
echo "- Improve API1: ./improve-api1.sh (333→500 TPS)"
echo "- Logs: tail -f app.log"
  1. chmod +x setting.sh
  2. 실행하면 쭈욱 깔림
  3. 컨트롤러에 api 3개가 있고, 1은 50ms, 2는 2ms, 3은 1ms가 걸림. 그리고 이를 병렬로 실행시키는 시나리오
  4. 다른 ec2에 setting.sh 생성 후 복붙(내용은 부하 테스트 및 모니터링을 위한 것)
#!/bin/bash

set -e

echo "=== Monitoring Server Setup ==="

# 기본 패키지 설치
sudo apt update -y
sudo apt install -y wget curl apt-transport-https ca-certificates gnupg lsb-release gettext-base jq

# 작업 디렉토리 생성
mkdir -p ~/monitoring && cd ~/monitoring

# Docker 설치
echo "=== Installing Docker ==="
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update -y
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntu

# k6 설치
echo "=== Installing k6 ==="
sudo snap install k6
k6 version

# API 서버 연결성 확인
echo "=== Testing API Server Connection ==="
if curl -s --connect-timeout 10 http://${API_SERVER_IP}:8080/health | grep -q "UP"; then
    echo "API Server is accessible"
    
    # 각 API 응답시간 확인
    echo "API 응답시간 확인:"
    curl -s http://${API_SERVER_IP}:8080/api/1 | jq -r '"api/1: " + .processingTime'
    curl -s http://${API_SERVER_IP}:8080/api/2 | jq -r '"api/2: " + .processingTime'  
    curl -s http://${API_SERVER_IP}:8080/api/3 | jq -r '"api/3: " + .processingTime'
    
else
    echo "API Server not accessible - check security group"
    exit 1
fi

# Prometheus 설정
cat > prometheus.yml.template << 'EOF'
global:
  scrape_interval: 5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: 'spring-boot-app'
    static_configs:
      - targets: ['${API_SERVER_IP}:8081']
    metrics_path: /actuator/prometheus
    scrape_interval: 2s
EOF

envsubst < prometheus.yml.template > prometheus.yml

# Docker Compose 설정
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-storage:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=200h'
      - '--web.enable-lifecycle'
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana
    depends_on:
      - prometheus
    restart: unless-stopped

volumes:
  prometheus-storage:
  grafana-storage:
EOF

# k6 테스트 스크립트 생성
cat > business-flow-test.js << 'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },    // 워밍업
    { duration: '2m', target: 100 },   // 점진적 증가
    { duration: '3m', target: 200 },   // 높은 부하
    { duration: '2m', target: 300 },   // 피크 부하 (병목 확인)
    { duration: '1m', target: 400 },   // 한계 테스트
    { duration: '1m', target: 0 },     // 종료
  ],
  thresholds: {
    http_req_duration: ['p(95)<100'],  // 엄격한 기준
    http_req_failed: ['rate<0.01'],
  },
};

const API_BASE_URL = `http://${__ENV.API_SERVER_IP}:8080`;

export default function () {
  const response = http.get(`${API_BASE_URL}/api/business-flow`);
  
  check(response, {
    'business-flow status is 200': (r) => r.status === 200,
    'business-flow response time < 50ms': (r) => r.timings.duration < 50,
  });
  
  sleep(0.01); // 매우 짧은 think time으로 높은 부하 생성
}
EOF

# 실행 스크립트들 생성
cat > run-business-flow-test.sh << 'EOF'
#!/bin/bash
echo "======================================================"
echo "병렬 처리 병목 분석 부하테스트"
echo "======================================================"

echo "Target: http://${API_SERVER_IP}:8080/api/business-flow"
echo "Monitor: http://${MONITORING_SERVER_IP}:3000"

read -p "Press Enter to start load test..."

API_SERVER_IP=${API_SERVER_IP} k6 run business-flow-test.js
EOF

cat > run-with-dashboard.sh << 'EOF'
#!/bin/bash
echo "======================================================"
echo "k6 실시간 대시보드 포함 테스트"
echo "======================================================"
echo ""
echo "k6 Dashboard: http://${MONITORING_SERVER_IP}:5665"
echo "Grafana: http://${MONITORING_SERVER_IP}:3000"

API_SERVER_IP=${API_SERVER_IP} k6 run --out web-dashboard=port=5665 business-flow-test.js
EOF

chmod +x run-business-flow-test.sh run-with-dashboard.sh

# 모니터링 스택 시작
echo "=== Starting Monitoring Stack ==="
sudo docker compose up -d

echo "=== Waiting for services to start ==="
sleep 30

echo "=== Service Status ==="
sudo docker ps

# 초기 메트릭 생성
echo ""
echo "=== Generating Initial Metrics ==="
echo "API 호출로 Grafana 메트릭 생성..."
# 초기 메트릭 생성
echo ""
echo "=== Generating Initial Metrics ==="
echo "API 호출로 Grafana 메트릭 생성..."
for i in {1..10}; do
  curl -s http://${API_SERVER_IP}:8080/api/business-flow > /dev/null
  echo "API call $i completed"
  sleep 1
done

echo ""
echo "======================================================"
echo "Monitoring Server Setup Complete!"
echo "======================================================"
echo ""
echo "Access URLs:"
echo "- Prometheus: http://${MONITORING_SERVER_IP}:9090"
echo "- Grafana: http://${MONITORING_SERVER_IP}:3000 (admin/admin)"
echo ""
echo "Load Test Commands:"
echo "- ./run-with-dashboard.sh"
echo ""
  1. 똑같이 권한 주고 실행
  2. 이 상태 이후에 api호출이 이뤄져야 실제 그라파나에서 매트릭으로 나타난다해서 그것을 호출하는 내용도 위에 있음. 그것이 진행됨
  3. 이제 부하 테스트 진행 완료
  4. 이제 외부 브라우저에서 shell의 url이 가리키는 그라파나 페이지로 접근
  5. 설정의 connection으로 접근
  6. prometheus 검색 후 add new data source.
  7. 커넥션을 입력해야하는데, 모니터링 ec2의 프로메테우스 컨테이너의 이름:9090을 그대로 적어야함
  1. 이제 대시보드 탭을 가서 대시보드 생성을 함
  2. visuallaction을 추가
  3. RPS기반 쿼리를 밑에 Code탭으로 가서 쿼리를 넣는 공간에 넣고 Run Queiries를 실행
  • rate(http_server_requests_seconds_count{uri="/api/business-flow"}[1m])
  1. 또 add visuallation을 하여서 Responsetime을 가져오는 쿼리를 설정
  • rate(http_server_requests_seconds_sum{uri="/api/business-flow"}[1m]) / rate(http_server_requests_seconds_count{uri="/api/business-flow"}[1m]) * 1000
  1. 이제까지 하면 기본 쿼리 및 모니터링 성공

부하테스트 진행

  1. 모니터링ec2에 가서, /monitoring/run-with-dashboard.sh 를 실행
  2. 그럼이제 다양한 유저가 점점 올리면서 검사하는 스트레스 테스트가 진행됨
  3. 그라파나 가서 refresh를 5s로 진행
  4. 응답 시간과 RPS가 계속 늘어남
  5. 프로메테우스에서 메트릭을 볼수있음. ec2 쉘에 가서 프로메테우스 링크를 복사하여 다른 웹브라우저에 붙여넣기후 진입
  6. query탭에가서 쿼리에 이걸 실행
  • rate(http_server_requests_seconds_sum[1m]) / rate(http_server_requests_seconds_count[1m]) * 1000
  1. api마다 걸리는 딜레이 시간을 확인 가능
  2. 그런데 계속 진행 후에 갑자기 응답시간이 쭉 떨어지는 기간이있는데, 이 이유는 JIT 성능 최적화때문이라함
  3. k6자체에서도 대시보드가 있는데, 해당 부하테스트 쿼리문에 한해서만 자세하게 볼 수 있음

의의

API마다 몇ms걸렸는지 부하테스트를 걸어서, 특정 api에서 어떤 상황에서 오래걸리는지를 미리 파악하여 성능을 개선할 수 있음

그라파나 대시보드 종류

대시보드에서 import dashboard, 대시보드로 가면 사람들이 만든 그라파나 대시보드를 다양하게 있음. 이걸 다운 가능

  • mysql, mongodb, jira 등등 모두 가능
  • spring boot 3.x 로 가서 copy id to clipboard하여서, 그걸 import dashboard 창에 기입하고 import하면 대시보드가 나옴

의미

  • uptime : 실행시간
  • heap used: jvm의 힙 사이즈(배열, 객체...)
  • Non-heap used: 메타데이터 지역변수 등등 힙 제외 모든것
  • process open files: 현재 프로세스가 fp를 열고있는 개수
  • cpu 사용량: 초록은 system cpu(운영체제와 커널을 사용하는 비율), 노랑은 process cpu(프로세스가 사용하는 비율)
  • Load average: 프로세스 상태 중에서 R(Running)과 D(Disk Wait) 상태에 있는 프로세스들의 개수를 측정. 이는 CPU 코어가 처리해야 할 작업의 양을 나타내며, 시스템의 부하 상태를 파악하는 핵심 지표
profile
보안을 겸비하고픈 풀스택개발자

0개의 댓글