Skip to content

프로토타입에 소켓 부하테스트를 진행해보자

juhyojeong edited this page Dec 5, 2022 · 1 revision

들어가며

2022년 12월 1일자로 부크럼 프로토타입이 개발되었다. 아직 몇몇 기능(드로잉, 섹션 등) 구현이 남있지만 기본적인 소켓에 대한 동작은 모두 구현이 완료된 상태이다. 프로토타입은 우리의 가설과 개념을 검증하기 위한 수단이다. 프로토타입 개발 이후 몇몇 사용자에게 공개하였고(데모 공유 및 피어세션 타임) 긍정적인 반응을 이끌어내며 우리의 방향이 맞음을 다시 한번 확인했다. 다음으로 진행하는 부하테스트는 전체 시스템의 성능을 확인하기에 용이하다. 부하테스트를 통해 우리 프로토타입의 전체적인 성능 및 임계점을 파악한 후 이 결과를 토대로 우리 비즈니스에 맞는 기술적 요구사항을 도출하고자 한다.

테스트 목표

부하테스트를 통해 다음 3가지를 확인하고자 한다.

  1. 현재 최대한으로 몇명까지 입장이 가능한지
  2. create_object, update_object 등 db를 업데이트하는 이벤트들의 latency 측정
  3. mouse_move, move_object 등 자주 발생하고 소켓을 단순 경유하는 이벤트가 퍼포먼스에 얼마나 영향을 끼치는지

환경 설정

개발 환경

테스트 도구: "artillery": "2.0.0-6"

테스트 엔진: "artillery-engine-socketio-v3": "^1.1.2"

target: https://bc7m-j045.xyz/

부하 테스트 도구로는 socket.io 공식문서에서 추천하는 artillery를 사용한다. artillery의 socketio engine은 우리 서버에서 사용하는 socket.io v4와 호환되지 않는 v2를 사용하기 때문에 v3를 지원해주는 커스텀 엔진을 사용한다.

테스트 단계

config:
  target: "https://bc7m-j045.xyz"
  phases:
    - duration: 40
      arrivalRate: 1
      maxVusers: 5
      name: "small group"
    
    - duration: 40
      arrivalRate: 3
      maxVusers: 20
      name: "medium group"
    
    - duration: 40
      arrivalRate: 5
      name: "maxium point"

테스트 단계는 3단계로 이뤄져있다.

  • small group - 우리 서비스를 이용하는 가장 작은 단위인 최대 5명이 이용하는 것을 가정하여 테스트한다.
  • medium group - 우리 서비스를 이용하는 가장 높은 단위인 최대 20명이 이용하는 것을 가졍하여 테스트한다
  • maximum point - 초당 5명의 사용자를 늘리면서 서비스의 임계 지점을 테스트한다.

시나리오

시나리오는 총 4가지가 존재한다

  1. mouse pointer: 0.01초 간격으로 move_pointer 이벤트를 발생시켜 db 업데이트 없이 소켓을 경우하는 이벤트의 퍼포먼스를 측정하고자 한다.
scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - emit: ["move_pointer", { x: 500, y: 500 }]
          namespace: /workspace/9aed1cfb-fd82-46e7-a127-2b70208b2e8b
        - think: 0.01
        count: 30000**scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - emit: ["move_pointer", { x: 500, y: 500 }]
          namespace: /workspace/9aed1cfb-fd82-46e7-a127-2b70208b2e8b
        - think: 0.01
        count:{number}**
  1. text update: 객체를 생성하고 0.1초 간격으로 text를 수정하는 update_object를 발생시킨다. Text는 현재 유일하고 변경중에 db를 업데이트하는 이벤트기 떄문에 분리했다.
scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/8fdd56f8-96b7-4264-a61b-5312bc1ff877
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/8fdd56f8-96b7-4264-a61b-5312bc1ff877
          - think: 0.1
          count: 50
        count: 5
  1. db update: object를 생성하고 변경되었을 때 db를 업데이트하는 이벤트를 발생시킨다.
scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/229d1dfe-0823-4317-9c04-4eb029850b96
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", left: 0, right: 0 }]
            namespace: /workspace/229d1dfe-0823-4317-9c04-4eb029850b96
          - think: 1
          count: 14
        count: 3
  1. real context: 사용자가 실제로 사용한다는 가정으로 시나리오를 구성하였다.
scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
          - think: 0.01
          count: 500
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
          - think: 0.1
          count: 50
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
          - think: 0.01
          count: 500
        count: 3

테스트 결과

과정이 너무 길어서 결과를 위에 서술합니다

1. 현재 최대한으로 몇명까지 입장이 가능한지

테스트7의 결과로 인해 실제 활동하는 사용자 기준으로 최대 25명까지 가능 25명 초과부터는 timeout 에러가 발생

2.create_object, update_object 등 db를 업데이트하는 이벤트들의 latency 측정

latency의 문제는 생각보다 심각하지 않아보임, 주요 지표인 p95, p99이 튀는 테스트가 없었고 max가 가끔 높게 측정되었으나 단위가 ms인걸 감안하면 우려할 수치는 아님

하지만 다수의 이벤트 발생시 timeout이 발생하는것을 보아 latency는 지켜지지만 이를 위해 일부 이벤트를 포기하는 것처럼 보임 이벤트 수를 관리할 필요가 있음

3. mouse_move, move_object 등 자주 발생하고 소켓을 단순 경유하는 이벤트가 퍼포먼스에 얼마나 영향을 끼치는지

테스트8에서 테스트7와 다르게 move pointer 이벤트를 다른 방식으로 대체한다고 가정하고 테스트를 진행

기존에는 25명부터 서버가 불안했으나 변경 후에는 150명까지도 안전함을 확인 심지어 text update 상황이라 단순한 object update보다 훨씬 많은 이벤트가 갔음에도 안정적인 퍼포먼스를 보여줌

따라서 mouse_move, move_object와 같은 소켓을 단순 경유하지만 자주 발생하는 이벤트의 경우도 퍼포먼스 및 안정성에 많은 영향을 끼치고 있음 이에 대한 개선이 필요해보임

추가적인 느낀점

  1. 텍스트 업데이트 방식 → 일반 db 업데이트에 비해 10배가량 많은 요청 발생, 요청이 많을 수록 서버 안정성이 감소하므로 방식을 개선할 필요 있음
  2. 생각보다 퍼포먼스에는 문제가 없다. 안정성을 위해 클라이언트에서 서버로 보내는 요청의 수를 최적화 할필요가 있음
  3. 개선 중요하지만 우리 서비스의 타겟층 5 ~ 25명 사이는 꽤나 안정적인 상태 어느정도 개선을 할 것인지 팀원간 합의 및 우선순위 정리가 필요해보임

생각해본 개선사항

  1. canvas frame에 맞춰서 이벤트 배치 전송

    canvas의 frame에 비해 너무 많은 이벤트가 너무 짧은 시간에 전송되고 있음 → 서버에 대한 요청 부담 증가, 서버에서 빠르게 응답이 오더라도 어짜피 canvas에 반영이 안되고 버벅이는 것처럼 보이는 효과

  2. ~ing와 ~ed의 처리부 분리

    ~ing 이벤트는 ed 이벤트에 비해 너무 많은 이벤트를 발생시켜 안정성을 크게 낮춤 그렇다고 ~ing 이벤트의 이벤트수를 줄이면 ux 적으로 끊겨보일 수 있음 따라서 ~ing는 webrtc 등을 사용해 저장되지 않아도 되는 데이터로 관리하고 ed는 소켓에 보내 db에 저정하는 용도로 개선하면 안정성을 크게 늘릴 수 있어보임

테스트 진행

테스트1

시나리오: mouse pointer

테스트 단계: "small group" - "medium group" - "maxium point"

테스트 목적: 마우스 포인터 시나리오 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  phases:
    - duration: 40
      arrivalRate: 1
      maxVusers: 5
      name: "small group"
    - duration: 40
      arrivalRate: 2
      maxVusers: 20
      name: "medium group"
    - duration: 40
      arrivalRate: 5
      name: "maxium point"
    
  engines:
   socketio-v3: {}

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - emit: ["move_pointer", { x: 500, y: 500 }]
          namespace: /workspace/ac8d1ed5-a043-497f-a740-8fcb70f0dc35
        - think: 0.01
        count: 4000

첫 번째 테스트는 move_pointer 이벤트를 한 유저장 0.01초 간격으로 4000번 발생시켰다. 0.01초는 아래와 같이 일반적으로 move_pointer가 작동하는 시간텀을 파악하여 평균으로 설정하였다

image

move pointer 발생 주기

테스트1 - 결과

image

image

image

*Errors at intervals 그래프에 timeline이 문제가 있는 듯 하다. raw json 데이터를 찾아봐도 error가 대략 maximum 테스트가 시작될 때 터지기 시작하는데 시작하자마자 터지는 것으로 표시되어 있다. 아래에 maximum 없이 진행한 테스트와 비교하면 maximum 이후 터진게 확실해진다.

테스트2

시나리오: mouse pointer

테스트 단계: "small group" - "medium group"

테스트 목적: 일반적인 상황일 때 mouse pointer 부하테스트

테스트2 - 결과

image

image

image

테스트3

시나리오: text update

테스트 단계: "small group" - "medium group" - "maxium point"

테스트 목적: text update 시나리오 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  processor: "./processor/object.js"
  phases:
    - duration: 40
      arrivalRate: 1
      maxVusers: 5
      name: "small group"    
    - duration: 40
      arrivalRate: 2
      maxVusers: 10
      name: "medium group"
    - duration: 40
      arrivalRate: 5
      name: "maxium point"
  engines:
   socketio-v3: {}

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/86b6d7f3-bbbf-49e6-827e-409077ff6d5b
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/86b6d7f3-bbbf-49e6-827e-409077ff6d5b
          - think: 0.1
          count: 500
        count: 5

객체를 생성하고 text를 업뎃하는 update_object를 0.1초 간격으로 500번 보내는 행동을 5번 반복했다.

text update는 현재 유일하게 db 업뎃이 일어나는 이벤트 중 수정된 결과가 아닌 수정 중인 상태로 데이터를 전송하는 이벤트이다.

테스트3 - 결과

image

image

image

테스트4

시나리오: text update

테스트 단계: "small group" - "medium group"

테스트 목적: 일반적인 상황일 때 text update 시나리오 테스트

테스트4 - 결과

image

image

테스트5

시나리오: real context

테스트 단계: "small group" - "medium group"- "maxium point"

테스트 목적: real context 시나리오 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  processor: "./processor/object.js"
  phases:
    - duration: 40
      arrivalRate: 1
      maxVusers: 5
      name: "small group" 
    - duration: 40
      arrivalRate: 2
      maxVusers: 10
      name: "medium group"
    - duration: 40
      arrivalRate: 5
      name: "maxium point"
  engines:
   socketio-v3: {}

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/90b465b7-6fe4-468a-9d3f-c46ae25a2c96
          - think: 0.01
          count: 500
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/90b465b7-6fe4-468a-9d3f-c46ae25a2c96
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/90b465b7-6fe4-468a-9d3f-c46ae25a2c96
          - think: 0.1
          count: 50
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/90b465b7-6fe4-468a-9d3f-c46ae25a2c96
          - think: 0.01
          count: 500
        count: 3

실제 사용자가 사용하는것을 가정하고 작성한 스크립트, move_pointer 같은 RPS가 높은 이벤트 이후에 db 업데이트가 일어나는 이벤트를 발생시킬 때 퍼포먼스를 측정한다.

테스트5 - 결과

image

image

image

테스트6

시나리오: real context

테스트 단계: "small group" - "medium group"

테스트 목적: 일반적인 상황에서 real context 시나리오 테스트

테스트6 - 결과

image

image

테스트7: 부하지점 테스트

시나리오: real-context

테스트 단계: 1초에 한명씩 증가하면서 50명, 25명, 20명일 때 측정

테스트 목적: 실제 몇명의 사용자가 이용할 때 부하가 오는지 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  processor: "./processor/object.js"
  phases:
    - duration: 50 | 30 | 25
      arrivalRate: 1
      name: "small group"
  engines:
   socketio-v3: {}

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/7c4739f2-c4fb-4813-8894-450fed767edd
          - think: 0.01
          count: 500
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/7c4739f2-c4fb-4813-8894-450fed767edd
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/7c4739f2-c4fb-4813-8894-450fed767edd
          - think: 0.1
          count: 50
        - loop:
          - emit: ["move_pointer", { x: 500, y: 500 }]
            namespace: /workspace/7c4739f2-c4fb-4813-8894-450fed767edd
          - think: 0.01
          count: 500
        count: 3

테스트7 - 결과

image

image

image

25명까지는 문제 없으니ㅏ 26명 ~ 30명 사이일 때 부터 부하가오기 시작해 timeout이 발생한다.

테스트8

시나리오: real-context

테스트 단계: 1초에 한명씩 증가하면서 50명, 25명, 20명일 때 측정

테스트 목적: 테스트7와 다르게 마우스 포인터 이벤트가 소켓을 통해 오지 않을 때 퍼포먼스가 얼마나 개선될 것인지 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  processor: "./processor/object.js"
  phases:
    - duration: 50
      arrivalRate: 1
      name: "small group" 
  engines:
   socketio-v3: {}

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - think: 5
        - function: "getObjectData"
        - emit: ["create_object", { type: 'postit', objectId: "{{ objectId }}", left: 500, top: 500, width: 300, height: 300, color: "rgb(175, 188, 207)", text: "Text...", fontSize: 40, scaleX: 1, scaleY: 1 }]
          namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
        - think: 1
        - loop:
          - emit: ["update_object", { objectId: "{{ objectId }}", text: "{{ $randomString() }}" }]
            namespace: /workspace/b6f6f1fe-0fba-4a48-a166-39749f6e8f1c
          - think: 0.1
          count: 50
        - think: 5
				# > duration에 맞춰서 변동시킴 3, 5, 10
        count: 3

테스트8 - 결과

image

image

image

150까지도 오류 없음

테스트9

시나리오: mouse pointer

테스트 단계: small group을 300초 동안 진행

테스트 목적: mouse pointer를 길게 지속해도 오류가 없는지 테스트

config:
  target: "https://bc7m-j045.xyz"
  plugins:
    metrics-by-endpoint: {}
  phases:
    - duration: 300
      arrivalRate: 1
      maxVusers: 5
      name: "small group"
  engines:
   socketio-v3: {
    transports: ["websocket"]
   }

scenarios:
  - name: load-test-pointer
    width: 10
    engine: socketio
    flow:
      - loop:
        - emit: ["move_pointer", { x: 500, y: 500 }]
          namespace: /workspace/9aed1cfb-fd82-46e7-a127-2b70208b2e8b
        - think: 0.01
        count: 30000

테스트9 - 결과

image

image

테스트 10

시나리오: db update

테스트 단계: small group - medium - maximum

테스트 목적: db update와 text update의 퍼포먼스 차이 비교

테스트 10 - 결과

image

발견한 문제점

socket.io v3 적용하기

socket io 공홈이 오래된 것, artillery와 socket-v3의 문서가 다 다르다

report가 다르다?sm

image

image

small group 당시 emit 보내는 속도가 의도와 드름

image

v1 초반 스타트

image

v2 초반 스타트

image

0.01 think 결과

image

latency가 안옴

scenarios:
  - name: load-test-pointer
    engine: socketio
    flow:
      - loop:
        - emit: ["move_pointer", { x: 500, y: 500 }]
          namespace: /workspace/f15539ec-2e82-4fc4-858b-989451747cfd
        - think: 0.01
        count: 3000
      - think: 50

여기 engine만 이렇게

참고자료

http://softflow.io/성능테스트-vs-부하테스트/

📚 그라운드 룰

✏️ 컨벤션

🧑‍🏫 멘토링

📁 애자일 프로세스

기획
데일리 스크럼
스프린트 리뷰
스프린트 회고
트러블 슈팅
기타 산출물

📖 기술문서

Week2
Week3
Week4
Week5

🗂 참고문서

Clone this wiki locally