부하테스트 load testing

x·2024년 1월 26일
0

test

목록 보기
3/3
post-thumbnail

참고 도서 : 아마존 웹 서비스 부하 테스트 입문

github repository : https://github.com/copyNdpaste/load-test-with-locust

Why

  • 이벤트 시 트래픽이 많을 것으로 예상돼서 예상 트래픽을 감당할 수 있는 서버, DB 등의 스펙을 정하려고 한다.
  • 부하 테스트를 진행하면서 병목이 있다면 병목을 개선한다. 병목은 비효율적인 코드, 부족한 인프라 리소스, 제한된 DB 설정 등이 될 수 있다.

What

  • 요청 수가 많거나 오래 걸리는 작업에 대해 테스트한다.
    • 홈에서 지도상에 보여줄 데이터 응답하는 API 호출
    • 유저가 방문하는 이벤트 페이지 호출

How

부하 테스트는 다음과 같은 단계를 거친다.

  • 부하 테스트 도구 선택
  • 계획
  • 준비
  • 실행(실행 및 병목 확인)
  • 실행(원인 분석 및 시스템 개선 작업)
  • 보고서

환경

  • ec2, python, flask, rds mysql 5, ...

테스트 도구

  • Locust https://github.com/locustio/locust
  • 파이썬으로 테스트 시나리오를 작성할 수 있다. 코드 히스토리 관리가 가능하다. 리소스가 적게 든다. UI가 깔끔하고, 테스트 리포트가 간단하다.

설치 방법

pip install locust

프로파일링, 모니터링 도구

top : 서버 실시간 모니터링, linux계 OS에서 제공되는 명령어. 실시간으로 시스템 전체와 프로세스별 CPU, 메모리 사용량을 볼 수 있음. 명령어 : top.
사용자 프로세스 CPU 사용률은 미들웨어나 프로그램에 사용되는 CPU 사용률이다. 부하 테스트 중 이 수치가 높을 때는 CPU를 앱에서 사용하고 있는 비율이 높은 것으로 일반적으로 좋은 의미다.
시스템 프로세스 CPU 사용률이 높은 경우에는 파일이나 네트워크 입출력 등에 CPU가 사용되고 있다는 의미다. 너무 높으면 파일 입출력이 너무 많은 건 아닌지 등을 확인해봐야 한다.


htop은 mac에서 cpu별 사용량을 모니터링해준다. 특정 cpu 사용률이 높은데 다른 cpu 사용률이 높아지지 않는 현상을 발견할 때 유용하다. 설치 : brew install htop . 명령어 : htop

netstat : TCP 등 네트워크 통신 상태를 볼 수 있는 명령어. 웹 시스템이 고부하 상태일 때는 TCP/IP 통신 부분이 병목 구간일 수 있다. 명령어 : netstat. State가 TIME_WAIT된 접속 수를 확인해야 함. TIME_WAIT 개수가 많으면 TCP 포트가 부족해서 TCP/IP 통신을 할 수 없게 되고 DB 서버는 웹 서버의 포트 부족 문제로 접속할 수 없게 된다.

cloudwatch
네트워크 latency를 포함하지 않는 시스템 처리 laytency를 확인하기 위해 로드 밸런서 모니터링을 해야한다. LB의 throughput도 확인할 수 있다.

newrelic, datadog 등을 갖추고 있다면 좋다.

계획

목표값 설정

초당 2만 5천 개 요청 처리해야함

테스트 시나리오

지도 조회 검색엔진, 마이페이지 조회, 회원가입, 로그인, 이벤트 페이지 조회

테스트 준비

환경 구축

부하 테스트용 LB 생성
부하 테스트용 ec2 플랫폼 서버 생성
부하 테스트용 mysql rds 생성
부하 테스트용 postgresql rds 생성
부하 테스트용 webapp cloudfront 생성
Elasticache redis db 13, celery BROKER_URL 14, RESULT_BACKEND 15
web ecs 는 기존 development 사용함

현 상황 파악

평소엔 많으면 10초간 3000건, 1초당 300건임


mysql 모니터링, 설정

# 프로세스가 실행 중인 쿼리와 실행 시간 등 조회
show full processlist;

# slow 쿼리 log 설정 전 값 확인
show variables like 'slow_query_log';  # 슬로우 쿼리 로그 설정 ON/OFF
show global variables like 'long_query_time';  # slow query 기준 시간, 1이면 1초 이상 쿼리를 slow query로 판단함
show global variables like 'log_output';  #

# delete from mysql.slow_log where user_host is not null;
# -> [HY000][1556] You can't use locks with log tables.
#
TRUNCATE mysql.slow_log;
set global log_queries_not_using_indexes=0;
SET GLOBAL general_log=OFF;
set global slow_query_log=1;
SET GLOBAL log_output='TABLE';
set global long_query_time=1.5;
FLUSH LOGS;
select sleep(1.5);
select * from mysql.slow_log;

DB 커넥션 확인

show status;
show Processlist ;

show variables like 'thread_cache_size'; # 재사용을 위한 thread cache 수, 이 수보다 연결이 많아지면 새 thread를 생성함
SHOW VARIABLES LIKE 'max_connections'; # 최대 동시 연결 수

SHOW STATUS LIKE 'Max_used_connections'; # 현재까지 최대 연결 수
SHOW STATUS LIKE 'Max_used_connections_time'; # 최대 연결이 발생한 날짜

SHOW STATUS LIKE 'Threads_connected'; # 현재 연결 수


SHOW STATUS LIKE '%Threads%';
SHOW STATUS LIKE '%connections%';

use information_schema;
select * from GLOBAL_STATUS;

부하 테스트 실행

설정

.vscode/launch.json 에 실행 설정

...
{
    "name": "Run locust",
    "type": "python",
    "request": "launch",
    "module": "locust",
    "args": [
        "-f",
        "${file}",
        "--headless",
        "--users=5"
    ],
    "console": "integratedTerminal",
    "gevent": true,
    "envFile": "${workspaceRoot}/.env",
}, 
...

실행 방법

master 실행
locust -f app/locustfile.py --class-picker --master

worker 실행
locust -f app/locustfile.py --worker --processes 1

gui 접속
http://0.0.0.0:8089

부하 테스트 스크립트 실행
python app/run_single_user.py

테스트 실행, 병목현상 확인
plan
부하 테스트 도구에 초점을 맞춘 테스트를 한다
시스템에 줄 수 있는 부하의 상한, 측정할 수 있는 throughput 상한 계산
do
check
병목이 발생할 수 있는 부분 체크

  • 부하 테스트 도구
  • 대상 시스템
  • 네트워크

health check용 api 호출 시의 throughput, latency와의 차이를 확인한다

DB 서버의 CPU 사용량이 60~80%가 되면 throughput에 한계가 올 수 있으므로 이정도 부하가 오면 리소스 증가해야 함

코드

run_single_user.py은 스크립트가 시작되는 시작점이다. run_single_user를 사용하면 디버깅하기 좋다.
FastHttpUser를 상속받은 EveryChargeUserActions의 메서드 중에 부하 테스트 시 실행하고 싶은 메서드에 @task 데코레이터를 작성한다. 스크립트가 실행되면 @task 데코레이터가 있는 메서드가 실행된다.

from locust import between, task, FastHttpUser


class EveryChargeUserActions(
    FastHttpUser,
    CreateStationReviewMixin,
    GetStationMixin,
    UpdateStationReviewMixin,
    DeleteStationReviewMixin,
    GetDownloadMixin,
):
    host = os.getenv("API_HOST")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.jwt = None

    @task
    def create_station_review(self):
        super().create_station_review(client=self.client)
        self.quit()

    @task
    def get_stations_nearby(self):
        super().get_stations_nearby(client=self.client)

create_station_review가 여러번 실행되면서 서버에 부하를 준다.

class CreateStationReviewMixin:
    def __init__(self) -> None:
        logger.info("CreateStationReviewMixin")
        self.repository = Repository()

    def create_station_review(self, client):
        review_users = self.repository.get_station_review_users()
        if not review_users:
            return False
        review_user = random.choice(review_users)
        user = review_user[0]
        review: StationReview = review_user[1]
        user_id = user.id
        readable_id = user.readable_id
        last_reset_date = user.last_reset_date
        station_id = review.stat_id
        _jwt = get_jwt(readable_id=readable_id, last_reset_date=last_reset_date)
        response = None
        try:
            response: FastResponse = client.post(
                url="/api/test/stations/report",
                headers={
                    "accept": "application/json",
                    "Content-Type": "application/x-www-form-urlencoded",
                    "Authorization": f"Bearer {_jwt}",
                },
                data={"statId": station_id, "note": "test_report"},
            )
            # logger.debug(response)

            if response.status_code not in [200, 204]:
                raise Exception(
                    f"create_station_review response.status_code : {response.status_code} json : {response.json()}"
                )
        except Exception as e:
            logger.error(f"create_report e : {e} response : {response.__dict__}")
            logger.debug(f"user_id : {user_id}")
            logger.debug(f"station_id : {station_id}")

실행 결과 기록

locust gui를 이용해 요청 수, 에러 등을 모니터링하면서 응답 속도가 늦어지는 시점을 파악한다.
ec2, load balancer, rds 등을 cloudwatch, top, netstat 등으로 모니터링하고 해당 시점에 CPU, Memory 사용량이 많아 문제가 생긴 리소스를 찾는다.
해당 리소스의 자원을 늘리면서 부하 테스트 강도도 높인다.
위 단계를 반복하다가 원하는 throughput을 만족하는 리소스를 찾으면 부하 테스트를 완료한다.

이번 부하 테스트의 경우 rds 하나만 사용했는데 db 리소스가 부족해서 read replica를 추가했다. 부하 테스트 강도를 높여주니 ec2 CPU가 부족해져서 리소스를 높이는 쪽으로 결론이 났다.

https://github.com/wg/wrk?tab=readme-ov-file
https://blog.naver.com/liyingjie_cn

0개의 댓글