업데이트:


목푯값 설정 계산 예시

  1. 우선 예상 1일 사용자 수(DAU)를 정해 본다. : 6,000명
  2. 피크 시간대의 집중률을 예상해 본다. (최대 트래픽 / 평소 트래픽) : 200GB / 22.5GB = 8.889
  3. 1명당 1일 평균 접속 혹은 요청 수를 예상해 본다. : 50번
  4. 이를 바탕으로 Throughput을 계산한다.
    • Throughput : 1일 평균 rps ~ 1일 최대 rps
      • 1일 사용자 수(DAU) x 1명당 1일 평균 접속 수 = 1일 총접속 수
        • 6,000 x 50 = 300,000
      • 1일 총접속 수 / 86,400 (초/일) = 1일 평균 rps
        • 300,000 / 86,400 = 3.472
      • 1일 평균 rps x (최대 트래픽 / 평소 트래픽) = 1일 최대 rps
        • 3.472 x 8.889 = 30.863
    • Latency : 일반적으로 50 ~ 100ms 이하로 잡는 것이 좋다.
      • 50ms
    • 사용자가 검색하는 데이터의 양, 갱신하는 데이터의 양 등을 파악해 둔다.

트래픽(일)이란?

인터넷 사용자에게 사이트의 정보를 노출 시킬 수 있는 하루 동안의 용량을 말한다.

  • 하루 동안 10M짜리 동영상이 10번 노출되면 트래픽(일)은 100M(10M*10)

일 트래픽 계산 방법

방문 수 * 평균 페이지뷰 * 1페이지당 평균 바이트

  • 방문 수 : 하루 동안 사이트를 방문한 횟수
    • 300,000회
    • 1,000,000회
  • 평균 페이지뷰 : 방문한 사람에게 노출되는 평균 페이지 수
    • 50개
    • 100개
  • 1페이지당 평균 바이트 : 노출되는 페이지의 평균 용량
    • 1.5KB
    • 2KB

VUser 구하기

  • Request Rate: measured by the number of requests per second (RPS)
  • VU: the number of virtual users
  • R: the number of requests per VU iteration : 12
  • T: a value larger than the time needed to complete a VU iteration : 1
T = (R * http_req_duration) (+ 1s) ; 내부망에서 테스트할 경우 예상 latency를 추가한다.

VUser = (목표 rps * T) / R
  • 예를 들어, 두 개의 요청 (R=2)이 있고 왕복시간이 0.5s, 지연시간이 1초라고 가정할 때 (T=2) 계산식은 아래와 같다.

ex) VU = (300 * 2) / 2 = 300

T = (12 * 1) = 12

평균 VU = (3.472 * 12) / 12 = 3.472 -> 4

최대 VU = (30 * 12) / 12 = 30.863 -> 31

VUser가 100명, 500명일 때 각각 트래픽 계산

R = 11 (아래의 smoke.js 파일 등에 있는 VU iteration 당 요청 수가 총 11개이다.)

T = (11 * 1) = 11

VUser = (목표 rps * 11) / 11

VUser = 목표 rps = 1일 최대 rps

VUser = 100

100 = 목표 rps

1일 평균 rps * 8.889 = 100

1일 평균 rps = 11.25

1일 총접속 수 / 86,400 (초/일) = 11.25

1일 총접속 수 = 972,000

1일 사용자 수 * 30 = 972,000

1일 사용자 수 = 32,400명

VUser = 500

500 = 목표 rps

1일 평균 rps * 8.889 = 500

1일 평균 rps = 56.249

1일 총접속 수 / 86,400 (초/일) = 56.249

1일 총접속 수 = 4,859,913

1일 사용자 수 * 30 = 4,859,913

1일 사용자 수 = 161,997명

테스트 기록

  • Thread Pool 설정
  • Nginx 설정
  • 비동기 로깅 적용
  • Nginx -> ELB 교체
  • 스케일 아웃, 로드밸런싱 : WAS를 5개로 늘림.
  • DB Replication 적용 (Master 1대 / Slave 4대)

Smoke Test

smoke_test

  • 최소한의 부하로 구성된 테스트로, 테스트 시나리오에 오류가 없는지 확인할 수 있다.
  • 최소 부하 상태에서 시스템에 오류가 발생하지 않는지 확인할 수 있다.
  • VUser를 1 ~ 2로 구성하여 테스트한다.

smoke.js

import http from 'k6/http';
import {check, sleep} from 'k6';

export let options = {
  stages: [
    {duration: '1m', target: 1}, // ${duration} 동안 현재 사용자를 ${target}명으로 유지한다.
  ],
  thresholds: {
    http_req_duration: ['p(99)<50'], // 99% 개의 요청이 50ms 미만으로 완료되어야 한다.
  },
};

function getPage(APIS) {
  for (const api of APIS) {
    const response = http.get(api);
    check(response, {
      'response code was 200': (response) => response.status == 200,
    });
    sleep(4);
  }
}

export default function () {
  // 메인 페이지 접속
  // 인기글 조회
  const GET_PAGE_APIS = [
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/12461',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/18553',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/10847'
  ];

  getPage(GET_PAGE_APIS);
};

개선 전 : AWS ELB + WAS 총 1개

smoke-was-scale-out

계획
  1. 30초 동안 VUser 0명 → 1명 증가.
  2. 1분 동안 VUser 1명 유지.
  3. 30초 동안 VUser 1명 → 0명 감소.
  4. 99%의 요청이 각각 50ms 미만으로 완료되어야 한다.
결과
  • :white_check_mark: 모든 요청 성공.
  • :bangbang: 95%의 요청 응답시간 173ms.

로드밸런싱 적용 (WAS 총 5개)

  • :bangbang: 95%의 요청 응답시간 252ms로 응답 속도 느려짐.
    • 추측 : 1개의 WAS일 때는 애플리케이션 단에서 트랜잭션 관리를 어느 정도 해 주는데, 로드밸런싱을 적용하니 별도의 WAS 5개가 하나의 DB를 동시에 찔러서 과부하가 걸리나?

DB Replication 적용 (Master 1대 / Slave 4대)

  • :white_check_mark: 95%의 요청 응답시간 176ms로 원상복구 됨.

최종 결과

  • 성능 동일.

Load Test

테스트 기간

  • 일반적으로 Load Test는 보통 30분 ~ 2시간 사이로 권장한다.
  • 부하가 주어진 상황에서 DB Failover, 배포 등 여러 상황을 부여하며 서비스의 성능을 확인한다.

load_test

  • 애플리케이션 배포 및 인프라 변경(scale out, DB failover 등) 시에 성능 변화를 확인한다.
  • 외부 요인(결제 등)에 따른 예외 상황을 확인한다.
  • 말 그대로 시스템이 얼마만큼의 부하를 견뎌낼 수 있는가에 대한 테스트를 말한다.
  • 보통 Ramp up 이라 하여 낮은 수준의 부하부터 높은 수준의 부하까지 예상 트래픽을 꾸준히 증가시키며 진행하는 테스트로, 한계점의 측정이 관건이며, 그 임계치를 높이는 것이 목적이라 할 수 있다.
  • 일반적으로 “동시접속자 수” 와 그 정도의 부하에 대한 Response Time으로 테스트를 측정한다.
  • 예를 들어 1분동 안 10만 명의 동시접속을 처리할 수 있는 시스템을 만들 수 있는지가 테스트의 주요 관건이 된다.

load.js

import http from 'k6/http';
import {check, sleep} from 'k6';

export let options = {
  stages: [
    {duration: '1m', target: 100}, // ${duration}  동안 현재 사용자를 ${target}명으로 유지한다.
  ],
  thresholds: {
    http_req_duration: ['p(99)<50'], // 99% 의 요청이 50ms 미만으로 완료되어야 한다.
  },
};

function getPage(APIS) {
  for (const api of APIS) {
    const response = http.get(api);
    check(response, {
      'response code was 200': (response) => response.status == 200,
    });
    sleep(4);
  }
}

export default function () {
  // 메인 페이지 접속
  // 인기글 조회
  const GET_PAGE_APIS = [
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/12461',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/18553',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/10847'
  ];

  getPage(GET_PAGE_APIS);
};

개선 전 : AWS ELB + WAS 총 1개

load-was-scale-out

계획 (VUser = 100 = 1일 사용자 약 32,000명)
  1. 30초 동안 VUser 0명 → 100명 증가.
  2. 1분 동안 VUser 100명 유지.
  3. 30초 동안 VUser 100명 → 0명 감소.
  4. 99%의 요청이 각각 50ms 미만으로 완료되어야 한다.
결과
  • :white_check_mark: 모든 요청 성공.
  • :bangbang: 95%의 요청 응답시간 1.86s.

로드밸런싱 적용 (WAS 총 5개)

  • :bangbang: 95%의 요청 응답시간 2.61s로 응답 속도 느려짐.
    • 추측 : 1개의 WAS일 때는 애플리케이션 단에서 트랜잭션 관리를 어느 정도 해 주는데, 로드밸런싱을 적용하니 별도의 WAS 5개가 하나의 DB를 동시에 찔러서 과부하가 걸리나?

DB Replication 적용 (Master 1대 / Slave 4대)

  • :white_check_mark: 95%의 요청 응답시간 180.73ms로, Smoke Test만큼 빨라짐.
    • 맨 처음보다 속도 약 10배배 빨라짐.

최종 결과

  • 속도 약 10배 빨라짐.

Stress Test

stress_test

  • 서비스가 극한의 상황에서 어떻게 동작하는지 확인한다.
  • 장기간 부하 발생에 대한 한계치를 확인하고 기능이 정상 동작하는지 확인한다.
  • 최대 사용자 또는 최대 처리량을 확인한다.
  • 스트레스 테스트 이후 시스템이 수동 개입 없이 복구되는지 확인한다.

stress.js

import http from 'k6/http';
import {check, sleep} from 'k6';

export let options = {
  stages: [
    {duration: '1m', target: 500}, // ${duration} 동안 현재 사용자를 ${target}명으로 늘린다.
  ],
  thresholds: {
    http_req_duration: ['p(99)<50'], // 99% 의 요청이 50ms 미만으로 완료되어야 한다.
  },
};

function getPage(APIS) {
  for (const api of APIS) {
    const response = http.get(api);
    check(response, {
      'response code was 200': (response) => response.status == 200,
    });
    sleep(4);
  }
}

export default function () {
  // 메인 페이지 접속
  // 인기글 조회
  const GET_PAGE_APIS = [
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/12461',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/18553',
    '{접속 URL}/api/v1/posts/paging?offset=0&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=10&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/paging?offset=20&size=10&sort=LIKE_COUNT_DESC',
    '{접속 URL}/api/v1/posts/10847'
  ];

  getPage(GET_PAGE_APIS);
};

개선 전 : AWS ELB + WAS 총 1개

stress-was-scale-out

계획 (VUser = 500 = 1일 사용자 약 162,000명)
  1. 30초 동안 VUser 0명 → 500명 증가.
  2. 1분 동안 VUser 500명 유지.
  3. 30초 동안 VUser 500명 → 0명 감소.
  4. 99% 의 요청이 각각 50ms 미만으로 완료되어야 한다.
결과
  • :bangbang: 58%의 요청 처리 실패.
  • :bangbang: 95%의 요청 응답시간 17.24s.

로드밸런싱 적용 (WAS 총 5개)

  • :white_check_mark: 모든 요청 처리 성공.
  • :bangbang: 95%의 요청 응답시간 29s로 응답 속도 느려짐.
    • 추측 : 1개의 WAS일 때는 애플리케이션 단에서 트랜잭션 관리를 어느 정도 해 주는데, 로드밸런싱을 적용하니 별도의 WAS 5개가 하나의 DB를 동시에 찔러서, 과부하가 걸리나?

DB Replication 적용 (Master 1대 / Slave 4대)

  • :white_check_mark: 95%의 요청 응답시간 4.88s로 속도 빨라짐.
    • 맨 처음보다 속도가 약 3.5배 빨라짐.

최종 결과

  • 58%의 요청 처리 실패 -> 모든 요청 처리 성공.
  • 속도 약 3.5배배 빨라짐.

최종 결과 요약 [WAS Scale-out(WAS 총 5개) + DB Replication(DB 총 4개)]

  • Smoke Test
    • 성능 동일
  • Load Test
    • 속도 약 10배 빨라짐.
  • Stress Test
    • 58%의 요청 처리 실패 -> 모든 요청 처리 성공.
    • 속도 약 3.5배 빨라짐.

참고자료

SQL INSERT 반복문

drop procedure if exists myFunction;
DELIMITER $$
CREATE PROCEDURE myFunction() -- myFunction이라는 이름의 프로시져
BEGIN
    DECLARE i INT DEFAULT 1; -- i변수 선언, defalt 값으로 1 설정

    DECLARE min_user_id INT DEFAULT 8002;
    DECLARE max_user_id INT DEFAULT 16002;

    DECLARE min_post_id INT DEFAULT 10001;
    DECLARE max_post_id INT DEFAULT 20011;

    WHILE (i <= 48000) DO -- for문 작성(i가 48,000 이하인 동안 반복)

    insert ignore
    into
        likes
    (likes_id, created_at, last_modified_at, post_id, user_id)
    values
        (null, now(), now(), FLOOR(RAND() * (max_post_id - min_post_id + 1)) + min_post_id, FLOOR(RAND() * (max_user_id - min_user_id + 1)) + min_user_id);

    SET i = i + 1; -- i에 1 더하고 WHILE문 처음으로 이동
        END WHILE;
END$$
DELIMITER ; -- 구분 기호를 다시 ;로 바꿔주기
CALL myFunction;

-- FLOOR(RAND() * (max_post_id - min_post_id + 1)) + min_post_id

댓글남기기