성능테스트

hyelim·2023년 8월 17일
0
post-thumbnail

단위 테스트는 '특정 메소드를 실행하는 상황'에서 발생하는 문제를 사전에 찾기 위한 작업이고, 통합 테스트는 '여러 메소드와 외부 의존 모듈이 함께할 때' 발생하는 문제를 사전에 찾기 위한 작업

그리고 성능 테스트는 '트래픽이 많은 상황'에서 발생하는 문제를 사전에 찾기 위한 작업이다

트래픽이란 1초동안 서버로 요청되는 수를 의미하며 RPS(Request Per Second)라고 표현하기도 한다

트래픽이 많은 상황에서는 서버의 처리속도가 느려진다던지, 서버의 리소스 부족으로 더 이상 사용자의 요청에 응답할 수 없거나 버그가 발견되는 등의 상황이 생길 수 있다. 이러한 문제를 성능테스트를 통해 극복이 가능하다고 한다

즉, 이러한 맥락에서 볼때 성능테스트란 1초당 요청이 가장 많은 상황을 기준으로 서비스에서 발생하는 성능가용성 관련 문제를 찾아내는 작업이라고 표현할 수 있다

성능테스트 종류

성능테스트 툴

NGrinder, JMeter, Greenlet

성능테스트 지표

Request 지표

  • Visit Time: 접속한 페이지를 들어오고 나가는 데까지의 시간
  • Request Time: 요청하는데 걸리는 시간
  • Response Time: 요청에 응답이 오는데까지 걸리는 시간
  • Think Time: 응답이후 다음 요청까지 걸리는 시간
  • Request Interval: 요청과 다음 요청 사이의 간격시간
  • TPS: 단위 시간 당 서버에서 처리할 수 있는 양

System 지표

  • CPU Usage/Used: Host의 CPU 사용량 혹은 사용률
  • Memory Usage/Used: Host 의 Memory 사용량 혹은 사용률
  • Disk I/O Time: 요청을 디스크에 작성하거나 읽는데 소요된 시간
  • Network I/O Throughput: 네트워크 인터페이스에서 사용하는 초당 비트수
  • Active Thread Count: 활성 스레드 수

좋은 성능이란

두번째 사진의 모습이다

성능테스트 툴 분석

JMeter

Apache 재단에서 오픈소스로 만든 자바 기반의 성능 테스트 툴

  • 다양한 프로토콜 지원 HTTP, HTTPS, FTP, JDBC, LDAP 등
  • 다양한 리포트 형식 지원 HTML, JSON, XML 등
  • 다양한 플러그인을 지원해주고, 유지 보수 활발

  • Thread Group: 몇 개의 쓰레드가 동시에 요청을 보내는지
  • Sampler: 어떤 유저가 해야하는 액션
  • Listener: 응답을 받았을 때 취하는 동작
  • Configuration
    Sampler 혹은 Listener 가 사용할 설정 값
  • Assertion
    응답 결과의 성공 여부를 판단하는 조건

전반적인 과정

JMeter 설치 및 실행 → Thread Group 생성 → Sampler 생성 → Response Assertion 설정→ Listener 설정 → 성능 테스트 실행 → Report 확인

NGrinder

네이버에서 성능 측정을 목적으로 Jython(JVM 위에서 파이썬이 동작) 으로 개발된 오픈 소스 프로젝트

자이썬(Jython)은 파이썬 프로그래밍 언어를 100 퍼센트 순수한 자바로 작성하여 완전하게 구현한 것

  • Custom Library 를 활용한 테스트 가능
  • Web UI 제공
  • 부하를 주는 Agent 와 부하를 받는 Target 서버에 대한 모니터링
  • 동시에 여러 테스트 가능

한편 nGrinder 는 Grinder 라는 내부 엔진을 사용하며 nGrinder 는 이 엔진을 controller 와 agent로 감싸 다수의 테스트를 병렬처리할 수 있도록 한다. controller 와 agent 는 nGrinder 의 주요 요소이다.

  • Controller
    성능테스트를 위한 웹 인터페이스를 제공하는 역할을 한다. agent 를 관리하는 것이 controller 이며 테스트 통계를 보여주거나 사용자가 스크립트를 생성 또는 변경하는 것을 가능하게 한다

  • Agenet
    스크립트를 기반으로 프로세스를 실행하고 지정한 타겟에 실제로 트래픽을 발생시켜 스레드를 동작시키게 한다. Monitoring 모드에서 타겟 시스템의 성능을 모니터링 하는 것도 agent의 역할이다

  • Monitor

전반적인 과정

nGrinder 설치 및 실행 → Groovy script 작성 → 성능테스트 변수 설정 → 성능 테스트 실행 → 레포트 확인

nGrinder 설치 및 실행

nGrinder 설치 및 실행은 도커 컴포즈를 이용한다 - localhost:8880 ( 계정정보는 admin, admin 이다)
nGrinder 는 로컬호스트나 127.0.0.1 을 인식하지 못해서 공인 IP 주소를 통해 실행이 가능하다

아래는 nGrinder 실행을 위한 docker-compose 파일이다

version: '3.7'

services:
  controller:
    container_name: ngrinder-controller
    image: ngrinder/controller:latest
    environment:
      - TZ=Asia/Seoul
    ports:
      - "8880:80"
      - "16001:16001"
      - "12000-12009:12000-12009"
    volumes:
      - /tmp/ngrinder-controller:/opt/ngrinder-controller
    sysctls:
      - net.core.somaxconn=65000
  agent-1:
    container_name: ngrinder-agent-1
    image: ngrinder/agent:latest
    links: 
      - controller
    environment:
      - TZ=Asia/Seoul
    sysctls:
      - net.core.somaxconn=65000    
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nproc:
        soft: 1024000
        hard: 1024000
      nofile:
        soft: 1024000
        hard: 1024000      
  agent-2:
    container_name: ngrinder-agent-2
    image: ngrinder/agent:latest
    links: 
      - controller
    environment:
      - TZ=Asia/Seoul
    sysctls:
      - net.core.somaxconn=65000    
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nproc:
        soft: 1024000
        hard: 1024000
      nofile:
        soft: 1024000
        hard: 1024000   

이렇게 controller, agent의 설치 후 해당 url에 접속하면 nGrinder 의 UI를 볼 수 있다

NGrinder 를 실행하면 자동으로 테스트 스크립트를 생성해준다

테스트 과정

작성예정

Locust

파이썬 스크립트 기반의 Load Test Framework

  • 높은 성능을 위해 비동기 라이브러리와 경량 쓰레드를 활용한 라이브러리 사용(Gevent=libdev + greenlet)
  • 비교적 단순한 파이썬 스크립트
  • Standalone, Distributed 환경 모두 지원(Local/ Master-worker 환경)
  • Web-UI 를 제공(Client-Host 등 설정 가능)

전반적인 과정

locust 설치 및 실행 → locustfile.py 작성 → Web UI 접속 및 성능 테스트 변수 설정 → 성능 테스트 실행 → Report 확인

docker-compose.yml

version: '3'

services:
  master:
    image: locustio/locust
    ports:
     - "8089:8089"
    volumes:
      - ./:/mnt/locust
    command: -f /mnt/locust/locustfile.py --master -H http://master:8089
  
  worker:
    image: locustio/locust
    volumes:
      - ./:/mnt/locust
    command: -f /mnt/locust/locustfile.py --worker --master-host master
  postgres:
    image: postgres
    container_name: locust-db
    restart: always
    ports:
      - "5440:5432"
    environment:
      - POSTGRES_DB=client
      - POSTGRES_USER=testuser
      - POSTGRES_PASSWORD=testpass

locustfile.py

#!/usr/bin/env python

# Copyright 2020 The klocust Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from locust import HttpUser, TaskSet, task, between, events, constant
from locust.exception import StopUser
import random

DEBUG_MODE = False

def print_log(url, response):
    if not DEBUG_MODE:
        return

    if response.status_code in [200, 201]:
        print(f'[{response.status_code}] {url}')
    else:
        print(f'[{response.status_code}] {url} {response.text}')

class WebsiteTasks(TaskSet):

    def __init__(self, parent):
        super().__init__(parent)
        self.default_headers = None

    # call with starting new test
    @events.test_start.add_listener
    def on_test_start(**kwargs):
        return

    # call with stopping new test
    @events.test_stop.add_listener
    def on_test_stop(**kwargs):
        return

    # get, post, put, delete helper methods
    def get(self, url, headers=None, name=None):
        response = self.client.get(url, headers=headers or self.default_headers, name=name or url)
        print_log(url, response)
        return response

    # call with starting new task
    def on_start(self):
        # self.login("email", "password")
        return

    # call with stopping new task
    def on_stop(self):
        # self.logout()
        return

    ######################################################################
    # write your tasks ###################################################
    ######################################################################
    @task(60)
    def getUserInfo(self):
        self.get("/ceres/api/userinfo/"+str(random.randrange(1,12)))

    @task(40)
    def getServiceName(self):
        self.get("/ceres/api/serviceName")

class WebsiteUser(HttpUser):
    tasks = [WebsiteTasks]

    # If you want no wait time between tasks
    # wait_time = constant(0)
    wait_time = between(1, 2)

    # default target host
    host = "http://192.168.120.109:8001"

https://judo0179.tistory.com/63
성능 테스트 가이드
https://fastcampus.co.kr/courses/213924/clips/

위의 블로그와 강의를 참고하여 작성하였습니다:)

profile
기록용

0개의 댓글