Performance Test

백엔드 애플리케이션 성능 테스트하기 강의를 요약한 내용입니다.

Intro

성능 테스트 툴을 활용하여 성능 테스트 결과를 반복하면서 예상되는 사용자 수와 그에 따른 요청 수를 계산할 수 있습니다.

  • 이러한 계산을 통해 얼마나 많은 수의 서버가 필요한지 테스트

성능 테스트가 필요한 상황

  • 비효율적으로 동작하는 애플리케이션 로직 개선 (GC ..)

  • 데이터베이스 같은 저장소에 대한 성능 개선 (Index, DeadLock ..)

  • 시스템 설계 개선 (비동기적인 구조, Circuit Breaker ..)

참고.

  • 트래픽: 네트워크를 통해 흐르는 데이터

  • 트래픽이 많고 적음의 기준은 상대적

  • 성능 테스트 = 부하 테스트 = 스트레스 테스트

배경지식

지연시간과 처리량

지연시간(Latency)

  • 클라이언트가 요청을 보낸 후 응답을 받기까지 걸린 시간

  • 주로 ms, s 단위

어떤 요청이 처리되어서 사용자에게 응답이 되기까지 1초가 걸렸다면 이 요청의 지연시간은 1초

처리량(Throughput)

  • 단위 시간동안 몇건의 요청을 처리할 수 있는지 (데이터의 전송량)

  • 주로 TPS(Transaction Per Second) 단위

어떤 API가 1초 동안 1,000개의 API 요청이 들어올 때 1,000의 요청을 문제없이 처리해낼 수 있다면
이 API의 처리량은 1,000 TPS

참고. 대역폭(Bandwidth)

  • 네트워크에서 유의미하게 사용될 수 있는 개념

    • 백엔드 애플리케이션에서는 여러 요소들의 간섭으로 정확한 대역폭 측정이 어려움

  • 네트워크가 단위시간 동안 전송할 수 있는 최대한의 처리량

  • 해당 네트워크를 최대한으로 활용했을 때 얼마나 빠른 속도로 많은 데이터를 보낼 수 있는가

  • 처리량을 계속 올리면서 테스트하다 보면 대역폭에 가까워질 수 있음

.

성능 테스트 목표

  • 성능 테스트 목표는 예상되는 서비스 이용자의 숫자와 사용자들이 수용할 수 있는 지연시간

  • 성능을 측정 시 처리량지연시간을 모두 측정

  • 사용자들이 불편함을 느끼지 않도록 처리하려면 어느 정도의 지연시간 미만으로 처리가 되어야 하는지의 식

  • 성능 테스트에는 많은 변수가 있으므로 95~99% 수준의 목표를 적용

초당 3000개의 요청(처리량)이 들어올 때 99%의 요청이
100ms 미만으로 처리(지연시간)되어야 함

요청을 초당 1개만 처리하면 지연시간이 낮게 나오겠지만,

처리량을 늘리면 지연시간도 함께 늘어날 가능성이 높다.

운영체제와 서버 자원

운영체제(OS, Operating System)

  • 서버의 물리적인 자원을 관리하며 프로세스라는 단위로 애플리케이션을 실행

자원내용사용

CPU

프로세스 실행

계산 작업, 이미지, 영상 인코딩

암호화, 해시화

RAM

프로세스 = 메모리(Stack, Heap..) + 코드

CPU와 함께 많이 사용

인스턴스 대량 생성, 캐싱, 컬렉션 객체 등

DISK

Program

파일 입출력, 로그 대량 발생

DB 데이터 대량 입출력

CPU

  • 프로세스가 가진 코드에 따라 실행시키면서 메모리에 있는 내용을 읽거나 쓰기

RAM

  • 프로세스: 현재 실행 중인 프로그램 (메모리 + 코드)

    • 메모리코드에 따라 수동적으로 사용되는 형태

  • 페이징: 메모리 내용 중 일부를 디스크의 가상 메모리에 저장했다가 불러오는 과정

DISK

  • 프로그램: 디스크에 저장되어 있는 실행될 수 있는 파일이나 소프트웨어 집합(exe, jar ..)

  • 프로그램을 실행시키면 프로그램은 메모리 위로 올라와서 실행(=프로세스)

지연시간, 처리량과의 관계

  • (1) 클라이언트 요청 증가

  • (2) 처리량 증가

  • (3) 서버 자원 사용량 증가

  • (4) 프로세스, 스레드 간 자원 사용을 위한 대기 시간 증가

    • 경쟁적인 상태 -> 처리량이 기대 이상으로 감소

  • (5) 지연 시간 증가

  • (6) 미처리 요청 증가

  • (7) 요청 중 일부 실패

프로세스 실행을 위해 CPU, Memoery, Disk가 서로 상호작용

성능 테스트 시 서버가 가지고 있는 자원 중 사용률이 높아지는 자원이 있다면 해당 자원의 사용률을 낮추는 방법을 고민해 보자.

효과적으로 성능 테스트를 하고 성능을 개선하려면 서버 자원에 대한 모니터링도 중요.

네트워크

네트워크는 성능 테스트에서 중요한 요소

  • 네트워크는 물리적인 거리의 제약을 많이 받음(라우터에서의 연산도 필요)

API 내에서 외부 서버(데이터 베이스, 외부 API) 호출 시 네트워크 통신이 발생

  • 데이터베이스 같은 경우 캐시를 활용하거나 DB 응답이 빨라지도록 개선 가능

데이터베이스

자원사용

CPU

사용자가 원하는 데이터인지 확인

RAM

데이터를 CPU가 연산할 수 있게 준비 혹은 캐시

DISK

데이터들이 저장

데이터베이스 지연시간이 길어지는 상황

  • 짧은 시간동안 많은 요청이 들어오는 경우 (처리량 증가)

  • 많은 데이터 중 필요한 데이터를 찾을 경우

    • 인덱스로 개선 가능

  • 한번에 많은 데이터를 응답으로 줘야 할 경우

  • 락이 너무 자주 걸리는 경우

스레드 풀과 커넥션 풀

기본 설정값이 있다보니 성능 테스트에서 튜닝이 필요한 자원

  • 성능 테스트에서 가장 먼저 문제가 나타나는 요소

Thread Pool

  • 서버가 요청을 받아서 처리하는 자원

  • 톰캣의 경우 기본적으로 200개의 스레드가 생성

  • 스레드들은 미리 생성되어 요청을 처리한 후 반납

  • 지연 시간이 길어질 경우

    • 모든 스레드는 사용중인 상태가 되고, 일부 요청은 큐에 저장되지만 큐도 모두 찰 경우 이후 요청은 버려지게 된다.

    • 스레드 양을 무작정 늘리게 되면 적은 양의 스레드보다 처리량이 낮아질 수 있다.

    • 이 경우 로드 밸런싱을 통해 여러 서버가 트래픽을 받도록 하거나 비동기로 요청을 처리하는 개선이 필요

Connectino Pool

  • 서버와 데이터베이스가 데이터를 주고 받기 위한 자원

  • 스레드 풀과 동일한 문제 존재

스레드 풀과 커넥션 풀은 여러 번의 성능 테스트를 통해 적절한 숫자를 찾아 설정하자.

성능 테스트의 방향성

(1) 한 건씩 요청을 보내서 지연시간이 어느정도 나오는지 테스트

(2) 처리량을 높이면서 현재 인프라 구성에서 지연시간이 치솟는 지점 찾기

(3) 어떤 부분이 병목이 되는건지 가설을 세워보고 서버 자원 모니터링, 로그 등을 통해 병목 지점 탐색

(4) 병목을 해결할 수 있는 방법 적용

Artillery

Get Started > Get Artillery

# install node
$ brew install node

# check node version
$ node -v

# install artillery
$ npm install -g artillery@1.7.6

# check artillery version
$ artillery --version

First Artillery Test

Create an Artillery test script

기본 테스트 스크립트 작성

config:
  target: 'http://localhost:8080'
  phases:
    # 60초동안 매 초마다 5개 요청(Throughput: 5)
    - duration: 60
      arrivalRate: 5
scenarios:
  - flow:
    - get:
        url: "/hello"

성능 테스트 실행

$ artillery run --output report.json test-config.yaml 
  • test-config.yaml 파일을 사용해서 성능 테스트를 진행하고 결과로 report.json 파일 생성

성능 테스트 로그

...

Report @ 00:00:00(+0000) 2024-00-00
Elapsed time: 1 minute, 0 seconds
  Scenarios launched:  50
  Scenarios completed: 50
  Requests completed:  50
  Mean response/sec: 5
  Response time (msec):
    min: 0
    max: 4
    median: 2
    p95: 3
    p99: 4
  Codes:
    200: 50

...

All virtual users finished
Summary report @ 00:00:00(+0000) 2024-00-00
  Scenarios launched:  300
  Scenarios completed: 300
  Requests completed:  300
  Mean response/sec: 4.98
  Response time (msec):
    min: 0
    max: 100
    median: 2
    p95: 3
    p99: 5
  Scenario counts:
    0: 300 (100%)
  Codes:
    200: 300
  • min, max, median은 latency를 의미

  • p95, p99는 각각 95%, 99% 사용자가 어느 정도르 latency를 느끼고 있는지

  • 200 Codes는 1초마다 5개의 요청을 하므로 10초당 50개

  • 최공 결과에는 1초 * 5개 * 60초 = 300개의 결과

결과 파일(report.json) html 파일로 변환

$ artillery report report.json --output report.html

Scenario Test

Reference > Test Scripts

시나리오 테스트 스크립트 작성

config:
  target: 'http://localhost:8080'
  phases: # 성능 테스트의 요청 설정
    # 30초 동안 초당 3개의 요청
    - duration: 30
      arrivalRate: 3 # 시나리오 개수만큼 요청
      name: Warm up # 페이지 이름
    - duration: 30
      arrivalRate: 3
      rampTo: 30 # 점점 요청 횟수를 늘려서 최종 초당 30개의 요청
      name: Ramp up load
    - duration: 60
      arrivalRate: 30
      name: Sustained load
    - duration: 30
      arrivalRate: 30
      rampTo: 10 # 점점 요청 횟수를 줄여서 최종 초당 10개의 요청
      name: End of load
scenarios: # 한 명의 사용자가 어떤 순서로 요청을 할지 설정
  - name: "login and use some functions" # 1번 시나리오 이름
    flow:
      - post:
          url: "/login"
      - get:
          url: "/some-function-one"
      - get:
          url: "/some-function-two"
  - name: "just login" # 2번 시나리오 이름
    flow:
      - post:
          url: "/login"

성능 테스트 로그

# 성능 테스트 실행
$ artillery run --output secario-report.json scenario-test-config.yaml

...

All virtual users finished
Summary report @ 00:00:00(+0000) 2024-00-00
  Scenarios launched:  3025
  Scenarios completed: 3025
  Requests completed:  6115
  Mean response/sec: 40.47
  Response time (msec):
    min: 15
    max: 64
    median: 24
    p95: 36
    p99: 39
  Scenario counts:
    login and use some functions: 1545 (51.074%)
    just login: 1480 (48.926%)
  Codes:
    200: 6115


# 결과를 html 파일로 변환
$ artillery report secario-report.json --output secario-report.html

Parameter Test

payload - loading data from CSV files

파라미터 테스트 스크립트 작성

config:
  target: 'http://localhost:8080'
  phases:
    - duration: 30
      arrivalRate: 3
      name: Warm up
  payload:
    path: "id-password.csv" # 파일 경로
    fields: # 필드 구분
      - "id"
      - "password"
scenarios:
  - name: "just login"
    flow:
      - post: # json 파라미터
          url: "/login-with-id-password"
          json:
            id: "{{ id }}"
            password: "{{ password }}"
  - name: "find user"
    flow:
      - get: # url 파라미터
          url: "/search?query={{ id }}"

성능 테스트 로그

# 성능 테스트 실행
$ artillery run --output parameter-report.json parameter-test-config.yaml

...

All virtual users finished
Summary report @ 00:00:00(+0900) 2024-00-00
  Scenarios launched:  90
  Scenarios completed: 90
  Requests completed:  90
  Mean response/sec: 2.98
  Response time (msec):
    min: 1
    max: 56
    median: 3
    p95: 4
    p99: 36
  Scenario counts:
    just login: 46 (51.111%)
    find user: 44 (48.889%)
  Codes:
    200: 90


# 결과를 html 파일로 변환
$ artillery report parameter-report.json --output parameter-report.html

High Load Test

파라미터 테스트 스크립트 작성

config:
  target: 'http://localhost:8080'
  phases:
    - duration: 30
      arrivalRate: 20
      name: Warm up
    - duration: 10
      arrivalRate: 20
      rampTo: 200
      name: Ramp up load
    - duration: 10
      arrivalRate: 200
      name: Sustained load
    - duration: 30
      arrivalRate: 200
      rampTo: 20
      name: End of load
scenarios:
  - name: "high load cpu"
    flow:
      - get:
          url: "/high-load-cpu"
 - name: "high load memory"
   flow:
     - get:
         url: "/high-load-memory"

성능 테스트 로그

# 성능 테스트 실행
$ artillery run --output high-load-report.json high-load-test-config.yaml

...

All virtual users finished
Summary report @ 00:00:00(+0900) 2024-00-00
  Scenarios launched:  7214
  Scenarios completed: 6767
  Requests completed:  6767
  Mean response/sec: 87.59
  Response time (msec):
    min: 2
    max: 7781
    median: 1823
    p95: 4456
    p99: 5369
  Scenario counts:
    high load cpu: 3669 (50.859%)
    high load memory: 3545 (49.141%)
  Codes:
    200: 6767
  Errors:
    ECONNRESET: 14
    ETIMEDOUT: 433



# 결과를 html 파일로 변환
$ artillery report high-load-report.json --output high-load-report.html

참고

다른 서능 테스트 툴

정상적인 테스트를 위해 애플리케이션과 성능 테스트를 분리된 서버에서 실행 권장

Last updated