Skip to content

Release v1.0.1

Latest
Compare
Choose a tag to compare
@zero0205 zero0205 released this 24 Jan 08:04
350d0cb

채팅 서버 개선

Artillery 부하테스트 진행

현재 구현되어 있는 채팅 서버가 어느 정도의 사용자와 네트워크 통신을 감당할 수 있는지 한계를 파악하고, 개선 전후의 차이를 수치로 비교하기 위해 부하테스트를 진행했습니다.

부하테스트 툴 선정

다양한 부하테스트 툴을 비교 분석한 결과, Artillery를 선택했습니다:

테스트 환경 구성

[Socket.io](http://socket.io/) 엔진을 사용한 테스트를 위해 artillery-engine-socketio-v3를 설치하고, 다음과 같은 요소들을 구현했습니다:

  1. JWT 인증 구현
    • ExtraHeaders를 활용하여 Socket 요청에 인증 토큰 포함
    • 모든 [Socket.io](http://socket.io/) 연결에 동일한 인증 헤더 적용
  2. 테스트 시나리오 작성
    • Before Hook 시도
      • Artillery의 before hook을 이용해 createRoom 이벤트 발생 시도
      • 테스트 실행 전 단계에서는 Socket.io 연결이 설정되지 않아 실패
    • Artillery 커스텀 코드 작성
      • 첫 번째 사용자만 방을 생성하도록 커스텀 함수 구현
      • 방장의 소켓 연결 종료 시 방이 자동 삭제되는 서버 구현 특성으로 인해 실패
    • 최종 해결 방안
      • Socket.io-client를 활용하여 테스트 실행 전 방 생성
      • 쉘 스크립트를 작성하여 프로세스 관리

앞으로의 개선 계획

부하테스트 결과를 바탕으로 다음과 같은 개선을 진행할 예정입니다:

  1. Redis 도입
    • Pub/Sub 패턴을 활용한 실시간 메시지 처리
  2. Redis Cluster 구성
    • 데이터 분산 저장으로 성능 향상
    • 자동 장애 복구(Auto-Failover) 지원
    • 고가용성 확보

추가 정리 자료

Artillery 부하테스트 진행기:

https://tested-tangelo-8a6.notion.site/17ce2e24f22780cf9511d7a6f32419a1?pvs=4
부하테스트 Github:

https://github.com/huiseon37/camon-load-test

미디어 서버 개선

테스트 개요

  • 테스트 기간: 총 1분 4초
  • 가상 사용자(VU) 수: 총 600명
  • 테스트 시나리오: 각 VU가 4번의 이벤트를 발생시킴 (총 2400회 이벤트 발생)

주요 지표

  1. 방송 송출 이벤트 발생률 (engine.socketio.emit_rate)
    • 평균: 초당 20회
    • 최대: 초당 40회
  2. 세션 길이 (vusers.session_length)
    • 최소: 3011.9ms
    • 최대: 3077.6ms
    • 평균: 3047.7ms
    • 중앙값: 3072.4ms
  3. 가상 사용자 생성 및 완료
    • 생성된 VU: 600
    • 완료된 VU: 600
    • 실패한 VU: 0

성능 분석

  1. 안정성
    • 모든 가상 사용자가 오류 없이 테스트를 완료했습니다 (vusers.failed: 0).
    • 이는 서비스가 주어진 부하 하에서 안정적으로 작동했음을 의미합니다.
  2. 응답 시간
    • 평균 세션 길이가 약 3초(3047.7ms)로 일정하게 유지되었습니다.
    • 최소와 최대 세션 길이의 차이가 작아(약 65ms), 응답 시간이 일관성 있게 유지되었음을 알 수 있습니다.
  3. 처리량
    • 서비스가 초당 평균 20개의 이벤트를 안정적으로 처리했습니다.
    • 최대 처리량은 초당 40개 이벤트로, 시스템이 일시적인 부하 증가도 잘 처리했음을 보여줍니다.

결론

  1. 테스트 기간 동안 서비스는 안정적으로 작동했으며, 오류 없이 모든 요청을 처리했습니다.
  2. 응답 시간이 일정하게 유지되어 사용자 경험 측면에서 좋은 성능을 보였습니다.
  3. 초당 20-40개의 이벤트를 처리할 수 있는 능력을 보여주었습니다.

진행 척도

  • 현재 로컬에서 mediasoup과 부하테스트가 가능한 사실까지 알아놓은 상태

앞으로의 개선 사항

  • 디테일한 테스트 시나리오 구축 필요 → 테스트 진행
  • 이후 redis를 통한 수평확장 가능 구조로 변환 → 수평확장
  • 개선된 상태에서의 부하테스트 → 분석

CI/CD 방식 개선

Jenkins 설치 및 초기 설정

docker pull jenkins/jenkins
docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts

플러그인 설치

  • Git Plugin: Github과 연동을 위해 설치
  • Docker Pipeline Plugin: Docker와의 통합 관리를 위해 설치
  • SSH Plugin: Pipeline에서 SSH를 쉽게 사용하기 위해 설치

기존 파이프라인 수정

pipeline {
    agent any
        environment {
            SERVER_SSH_KEY = credentials('SSH_KEY') // SSH 키
        }
    stages {
        stage('Login to Docker Hub and Build Docker Image') {
            steps {
                withCredentials([
                    usernamePassword(
                        credentialsId: 'Docker',
                        usernameVariable: 'DOCKER_USERNAME',
                        passwordVariable: 'DOCKER_PASSWORD'
                    )
                ]) {
                    sh '''
                        docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
                        docker buildx create --use --name mybuilder || echo "Builder already exists"
                        echo "${DOCKER_USERNAME}"
                        docker buildx build --platform linux/arm64 --push \
                            --tag ${DOCKER_USERNAME}/blog-nest:latest \
                            -f ./Dockerfile .
                    '''
                }
            }
        }
        stage('Copy Docker Compose to Server') {
            steps {
                sshagent(['SSH']){
                    script {
                        sh """
                            scp -o StrictHostKeyChecking=no -P ${params.SERVER_PORT} docker-compose.yml ${params.TARGET_HOST}:/home/hanseungheon/blog
                        """
                    }
                }
            }
        }

        stage('Create .env File on Server') {
            steps {
                sshagent(['SSH']) {
                    sh """
                        ssh -o StrictHostKeyChecking=no -p ${params.SERVER_PORT} ${params.TARGET_HOST} << EOF
                        mkdir -p /home/hanseungheon/blog
                        cat > /home/hanseungheon/blog/.env << 'ENV'
                        DB_LOCAL_HOST=${params.DB_HOST}
                        DB_PORT=${params.DB_PORT}
                        DB_USERNAME=${params.DB_USERNAME}
                        DB_PASSWORD=${params.DB_PASSWORD}
                        DB_DATABASE=${params.DB_DATABASE}
                        JWT_SECRET=${params.JWT_SECRET}
                        SERVER_URL=${params.SERVER_URL}
                        ENV
                        EOF
                    """
                }
            }
        }

        stage('Deploy to Server') {
            steps {
                withCredentials([
                    usernamePassword(
                    credentialsId: 'Docker',
                    usernameVariable: 'DOCKER_USERNAME',
                    passwordVariable: 'DOCKER_PASSWORD')
                ]){
                    sshagent(['SSH']) {
                        sh """
                            ssh -o StrictHostKeyChecking=no -p ${params.SERVER_PORT} ${params.TARGET_HOST} << 'EOF'
                            cd /home/hanseungheon/blog
                            sudo docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD}
                            sudo docker pull ${DOCKER_USERNAME}/blog-nest:latest
                            sudo docker tag ${DOCKER_USERNAME}/blog-nest:latest blog-nest
                            sudo docker-compose up -d
                            sudo docker image prune -f
                            sudo rm .env
EOF
                        """
                    }
                }
            }
        }
    }
}

결론

  • Docker 캐싱을 통해 빌드 및 배포 시간 단축

앞으로의 개선 사항

  • Jenkins 파이프라인의 추가 최적화를 통해 테스트 및 빌드 속도를 더욱 개선
  • Jenkins 에이전트 노드에 Auto Scaling을 도입해, 빌드 및 배포 트래픽 증가 시 유동적으로 빌드 자원을 확장

코드 품질 개선

1. eslint-config-airbnb 적용

eslint-config-airbnb를 도입하여 JavaScript/React 코드의 정적 분석을 수행하고, Airbnb 스타일 가이드에 맞춰 코드 컨벤션을 통일했습니다.

2. 타입 지정을 type alias로 통일

interface와 type alias가 혼재되어 있던 것을 하나로 통일했습니다. type alias가 IDE 미리보기 지원, 선언 병합으로 인한 혼란 방지 등의 장점이 있다고 생각하여 타입 지정은 type alias를 선택했습니다.

3. FSD 아키텍처로 마이그레이션

리팩토링 이전 구조가 결합도는 높고, 응집도는 낮아서 유지보수와 기능 추가가 어렵다고 생각하여 기능 중점의 아키텍처인 FSD로 마이그레이션을 진행했습니다.

개선 결과

// 개선 전 디렉토리 구조
└── src/
    ├── assets/
    ├── components/
    ├── constants/
    ├── contexts/
    ├── hooks/
    ├── pages/
    ├── services/
    ├── types/
    └── utils/
    
// 개선 후
📦src
 ┣ 📂app
 ┃ ┣ 📂layouts
 ┃ ┣ 📂providers
 ┃ ┗ 📂routes
 ┣ 📂features
 ┃ ┣ 📂auth
 ┃ ┣ 📂broadcasting
 ┃ ┣ 📂chatting
 ┃ ┣ 📂editProfile
 ┃ ┗ 📂watching
 ┣ 📂pages
 ┃ ┣ 📂Auth
 ┃ ┣ 📂Broadcast
 ┃ ┣ 📂Home
 ┃ ┣ 📂Live
 ┃ ┣ 📂Profile
 ┃ ┗ 📂Record
 ┗ 📂shared
   ┣ 📂api
   ┣ 📂contexts
   ┣ 📂lib
   ┣ 📂types
   ┗ 📂ui

개선 전에는 코드의 목적을 최우선으로 하여 디렉토리가 구분되어 있었습니다. 이렇게 하니 다음과 같은 문제점이 있었습니다.

  1. src/components의 평면적인 구조
  • 페이지 종속 컴포넌트는 src/pages 하위에 잘 분리되어 있으나, src/components 내부 구조에 문제가 있음
    • 성격이 다른 컴포넌트들이 구분 없이 한 폴더에 섞여있음
      • 실제 재사용 컴포넌트 (ChatContainer, Header 등)
      • SVG 아이콘 컴포넌트 (Icons)
    • 컴포넌트의 역할과 책임이 불명확함
  1. src/hooks의 평면적인 구조
  • 각기 다른 기능을 담당하는 훅들이 동일 레벨에 위치
    • 스트리밍 관련: useConsumer, useProducer, useMedia, useTransport
    • 인증 관련: useAuth,
    • api 관련: useAPI
    • 공통 기능: useSocket, useTheme, useToast, useIntersect
  • 관련 기능의 훅들이 분산되어 있어 코드 파악이 어려움
  • 훅 간의 의존성 관리가 복잡함
  1. 기능 확장의 어려움
  • 평면적인 구조로 인해 새로운 기능 추가 시 적절한 위치 선정이 어려움
  • 기존 코드와의 관계를 파악하기 어려워 리팩토링이나 기능 추가가 복잡해짐
  • 특히 인공지능 리팩토링 과정에서 기능을 추가하며 이러한 문제점들이 더욱 부각될 것으로 예상

그래서 FSD 구조로 개선을 했고, 다음과 같은 개선점을 찾을 수 있었습니다.

  1. 계층적 구조화
    • 기능별로 구분하고 위계를 정해놓으니 각 코드가 자신의 역할과 사용 범위를 명확하게 갖게 됨. 예를 들어 방송 송출 관련 코드는 features/broadcasting에 위치하고, 공통으로 사용되는 유틸리티는 shared에 위치하는 등 코드의 위치와 용도가 분명해짐.
    • 코드의 재사용성과 유지보수성이 향상됨
  2. 개발 효율성
    • 초기에는 러닝 커브가 높지만, 장기적으로 봤을 때는 리소스를 절약할 수 있다고 생각됨
    • 계층화된 구조(shared, entities, features 등)로 인해 새로운 기능이나 컴포넌트의 위치가 명확히 정의됨
  3. 공개 API 사용
    • 이전에는 여러 컴포넌트에서 공용적으로 사용되던 파일의 경로나 이름이 변경되면 사용되던 곳에서도 import하는 경로가 잘 바뀌었는지 확인해야 하는 불편함이 있었는데, 공개 API 사용으로 그런 불편함이 조금은 해소됨.

4. SonarQube를 사용한 정적 코드 분석

정적 코드 분석 도구인 SonarQube를 사용하여 리팩토링 이전의 코드와 FSD 아키텍처로 마이그레이션 이후의 코드를 비교 분석했습니다.

image

리팩토링 전

image

리팩토링 후

리팩토링하며 코드를 잘못 건드렸는지 좀 더 크리티컬한 이슈가 발생하기는 했지만, 이슈의 개수는 줄어든 것을 볼 수 있었습니다. SonarQube에서 찾아낸 이슈들도 현재 해결 중이니 다 해결한다면 코드 품질 향상은 확실히 확인할 수 있을 것으로 보입니다.

추가 정리 자료

eslint-config-airbnb 적용 : https://intelligent-broker-ff0.notion.site/2-1-eslint-config-airbnb-ca3a5b1393194392b0e8282552b8f363?pvs=4

interface vs type alias : https://intelligent-broker-ff0.notion.site/2-2-interface-vs-type-alias-271ae91799da40b4a2f684a0b0fe5797?pvs=4

FSD로 마이그레이션 : https://intelligent-broker-ff0.notion.site/3-FSD-2be2c4b667864de8a741e3ffe3eb3f90?pvs=4

SonarQube를 사용하여 정적 코드 분석하기: https://intelligent-broker-ff0.notion.site/SonarQube-ddb9f36276084196a836371f4c38693a?pvs=4