2021 10 05
2021-10-05¶
부하 테스트¶
- 왜 필요한가? - 완벽한 서비스는 없어 - 현재 시스템이 어느 정도의 부하 견디는지 확인 - 한계치에서 병목이 생기는 지점 파악 필요 - 장애 조치와 복구를 사전에 계획해둘 필요
- 가용성 - 시스템이 서비스를 정상적으로 제공할 수 있는 상태 - 단일 장애점(SPOF)를 없애 확장성 있는 서비스를 만들어 가용성을 높이자! - 이중화??? - 단순히 장비를 여러개 증설... 어떤 DB 찌르느냐에 따라 다른 결과 응답받음 - DNS를 통해 트래픽 분산... 캐싱되므로 바로 몰라 - WAS만을 이중화... DB에 폭탄 날라오면 무용지물 - 따라서 모든 요소를 "다중화" 해야한다!
- 다중화 - 장애가 발생해도 예비 운용장비로 시스템 기능 계속할 수 있도록 하는 것 - 다중화의 대상: Server, Load Balancer, Network Device 등
- 부하? - 참고: https://brainbackdoor.tistory.com/117 - 부하 확인하기 1. top, uptime 등의 툴을 활용하여 Load Average 확인 2. Load Average가 낮은데 전송량이 오르지 않는다? - SW 설정이나, 오류, NW, 원격 호스트 측에 원인이 없는지 확인 3. Load Average가 높다. CPU 사용률이나 I/O 대기율 추이를 확인하자 - [CPU 부하가 높은 경우] - User/Kernel 프로그램 중 원인 확인 - 프로세스 상태가 CPU 사용시간 분석하고 원인 되는 프로세스를 확인하자 - strace 등을 통해 병목지점 좁혀나가자 - [I/O 부하가 높은 경우] - ps로 특정 프로세스가 극단적으로 메모리 소비하진 않나 확인 - 입출력이 많아 부하가 높다면 메모리 증설/데이터 분산/캐시서버 도입 검토하자 - 부하 계산법 - HW는 주기적으로 CPU에 타이머 인터럽트 신호 보냄 - 타이머 인터럽트마다 Load Average 값이 계산됨 - Load Average: IO 대기 Task / 실행 가능한 Task - CPU 사용률 - 커널은 각 프로세스가 생성된 후 어느정도 CPU 시간 이용했는지를 프로세스 별로 기록함 (프로세스 어카운팅) - 스케줄러가 이를 토대로 CPU 과점유 프로세스 우선도 낮추거나 CPU 뻇거나 함 - I/O - 리눅스는 디스크에서 한 번 읽은 데이터 메모리에 캐시해둠 (페이지 캐싱) - 4KB 용량, 페이지라고 부름 - 디스크로 부터 데이터 읽는 것 == 페이지 캐시 구축하는 것 - 처리할 데이터량에 비례하여 메모리 증설할 것 - 증설할 수 없다면 데이터 분할해서 적재 적소에 올려라!
- 사용자 - Concurrent User: 사이트 띄워놓음. 언제든 부하를 줄 수 있는 사용자 - Active User(VUser): 메뉴나 링크 누르고 실제 결과 나오기를 기다리는 사용자
- 처리량 & 시간 - 처리량(TPS) - 서비스 처리 건수 / 측정 시간 - 요청 사용자 수 / 평균 응답 시간 - 동시 사용자 수 / 서비스 요청 간격 - 시간 - 브라우저 ~ 서버 구간: 정적 파일 크기, Connection 관리, 네트워크 환경 등에 영향 - 서버 구간: DB-어플리케이션 연결 문제, 프로그램 로직상 문제, 서버 리소스 부족 - 상위 5%의 화면이 95%의 사용자 요청 받는다는 점 기억하자!
- 테스트 - [목표] - 시스템의 응답 성능 및 한계치 알아보기 - 많은 부하 발생시 나타나는 증상 확인 - 서비스가 확장성을 가졌는지 확인 - [테스트 종류] - Smoke Test : 최소한의 부하로 구성 | 최소 부하 상태에서 시스템 오류 확인 | 1~2명의 VUser. - Load Test : 서비스의 평소 트래픽과 최대 트래픽에서 성능 확인 | 인프라 변경 시 성능 변화 확인 | 외부 요인에 따른 예외 상황 확인. - Stress Test : 극한의 부하로 구성 | 최대 사용자 또는 최대 처리량 확인 | 테스트 이후 자동 복구 확인 - [테스트 도구] - 덕목 - 시나리오 기반 테스트 가능해야 함 - 동접 수, 요청 간격, 최대 처리량 등 부하 조정할 수 있어야 함 - Apache JMeter, nGrinder, Gatling, Locust, K6 등의 도구 - [주의할 점] - 실제 사용자 접속 환경에서 진행할 것 - 테스트 DB의 데이터량이 실제 운영 DB와 동일할 것 - [테스트 계획] - 테스트하려는 Target 시스템 범위 정할 것 - 서비스 이용자 수, 사용자의 행동 패턴, 사용 기간 등을 고려 - 목푯값에 대한 성능 유지기간 정함 - 서버에 같이 동작하고 있는 다른 시스템, 제약 사항 파악 - [목푯값] 1. 예상 1일 사용자 수(DAU) 산정 2. 피크 시간대의 집중률 예상 3. 1명당 1일 평균 접속 요청수 예상 4. 이를 바탕으로 Throughput 계산 - 1일 총 접속 수 = 1일 사용자 수 * 1명당 1일 평균 접속 수 - 1일 총 접속 수 / 86400 = 1일 평균 request per second - 1일 평균 rps * (최대 트래픽 / 평소 트래픽) = 1일 최대 rps 5. Latency는 50~100ms 이하로 잡는 것이 좋음
K6 실습¶
- k6 설치
shell script $ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 $ echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list $ sudo apt-get update $ sudo apt-get install k6
- smoke.js 작성
# smoke.js import http from 'k6/http'; import { check, group, sleep, fail } from 'k6'; export let options = { vus: 1, // 1분동안 한 명의 유저가 루핑 duration: '10s', thresholds: { http_req_duration: ['p(99)<1500'], // 99%의 요청이 1.5초 내로 응답해야함 }, }; const BASE_URL = '[https://nolto.app]'; const USERNAME = 'joel'; const PASSWORD = 'joel'; export default function () { var payload = JSON.stringify({ email: USERNAME, password: PASSWORD, }); var params = { headers: { 'Content-Type': 'application/json', }, }; //OAuth 부터 문제네;; let loginRes = http.post(`${BASE_URL}/login/token`, payload, params); check(loginRes, { 'logged in successfully': (resp) => resp.json('accessToken') !== '', }); let authHeaders = { headers: { Authorization: `Bearer ${loginRes.json('accessToken')}`, }, }; let myObjects = http.get(`${BASE_URL}/members/me`, authHeaders).json(); check(myObjects, { 'retrieved member': (obj) => obj.id != 0 }); sleep(1); }; export let options = { stages: [ { duration: '1m', target: 500 }, // simulate ramp-up of traffic from 1 to 100 users over 5 minutes. { duration: '2m', target: 500 }, // stay at 100 users for 10 minutes { duration: '10s', target: 0 }, // ramp-down to 0 users ], thresholds: { http_req_duration: ['p(99)<1500'], // 99%의 요청이 1.5초 내로 끝나야함 'logged in successfully': ['p(99)<1500'], // 99%의 요청이 1.5초 내로 끝나야함 }, };
- 테스트 설정값 구하기 1. 목표 rps 구하기 2. VUser 구하기 - VUser = (목표 rps * T) / R - R: VUser의 iteration 마다의 요청수 - T: VUser가 iteration을 하기 위해 필요한 시간 (지연시간/왕복시간) - ex. 두 개의 요청(R=2), 왕복시간 0.5s, 지연시간 1s (T=2(지연시간/왕복시간))
- 예제 스크립트와 결과
import http from 'k6/http'; import { sleep, check } from 'k6'; export let options = { vus: 300, //300명의 VUsers duration: '10s', //10초간 유지 }; export default function () { const before = new Date().getTime(); const T = 2; http.get('http://test.loadimpact.com'); http.post('https://httpbin.test.loadimpact.com/anything'); const after = new Date().getTime(); const diff = (after - before) / 1000; const remainder = T - diff; check(remainder, { 'reached request rate': remainder > 0 }); if (remainder > 0) { sleep(remainder); } else { console.warn(`Timer exhausted! The execution time of the test took longer than ${T} seconds`); } }