데모데이 발표자료 - boostcampwm-2024/web08-BooQuiz GitHub Wiki

데모데이

매주 금요일 진행 되는 데모데이 시간에 발표한 자료 입니다.

1주차

원본 링크: https://www.figma.com/design/1CdBFnF3oWXgAzRdgEhRNU/Web08?node-id=82-305&t=TpeWEEVNNIxrTWHU-1

발표자료

1 - 표지 목차 팀소개 - 팀소개 팀소개 - 그라운드 룰 프로젝트 소개 - 프로젝트 목표 프로젝트 소개 - 핵심 기능 프로젝트 소개 - BooQuiz 프로젝트 관리 - 마일스톤 프로젝트 관리 - 기획2 프로젝트 관리 - 백로그 프로젝트 관리 - 백로그-1 프로젝트 관리 - 와이어프레임 프로젝트 관리 -기획

마지막 장

2주차 발표 자료

원본 링크: https://jacky0831.notion.site/2-12cf1897cdf58090a13cd0eed4440a74?pvs=4

발표자료

1. 프로젝트 개요

https://github.com/boostcampwm-2024/web08-BooQuiz

  • 프로젝트명: BooQuiz
  • 목적: 300명의 부스트캠퍼를 감당할 수 있는 실시간 퀴즈 플랫폼

2. 기술 스택

  • 프론트엔드: React, Vite, Tailwind, Shadcn UI, Typescript
  • 백엔드: Nest.js, Typescript
  • 공통: webSocket

3. 이번주 핵심 작업 내용

[공통] Husky 설정

github hook 활용을 위해 Husky를 적용하였습니다.

  • commit convention 적용
  • lint는 추후 적용 예정

[공통] 공통 타입 활용

모노레포 공통 패키지를 통해 프론트엔드, 백엔드에서 공용으로 사용할 수 있는 타입에 대해서 관리할 수 있도록 하려고 합니다.

[공통 타입 정리](https://www.notion.so/135f1897cdf580d486e5d8a814d6d738?pvs=21)

interface QuizZone {
	player: Player,
	quizzes: Quiz[],
	stage: 'LOBBY' | 'WAITING' | 'IN_PROGRESS' | 'COMPLETED' | 'RESULT'
	//퀴즈존 대기실 페이지 - 퀴즈 풀이 대기 페이지- 퀴즈 풀이 페이지- 퀴즈 제출 후 페이지 - 퀴즈존 결과 페이지
	currentQuizIndex: number,
	currentQuizStartTime: timestamp,
	currentQuizDeadlineTime: timestamp,
}
interface Quiz {
	index: number,
	question: string,
	answer: string,
}

[공통] 모각글 활동

1주차에 가볍게 모여서 글을 작성하는 시간을 가졌습니다. 토요일 오전 ☀️

[우리팀이 React를 선택하는 이유](https://www.notion.so/React-131f1897cdf5801bb3e5fc02f8c1257b?pvs=21)

[아키텍처 관점에서 ORM을 사용하는 이유](https://www.notion.so/ORM-c9460909c3434fb0b189228eacaf157e?pvs=21)

[WebRTC vs WebSocket](https://www.notion.so/WebRTC-vs-WebSocket-ec95f41abf0e4cf89a1440929f80bf19?pvs=21)

[클라이언트-서버 실시간 통신](https://www.notion.so/aed2e304596f49a08c9aeadc670a2d05?pvs=21)


[공통] 페어프로그래밍 시도

백엔드, 프론트엔드 파트를 나눠서 팀을 이뤄 페어프로그래밍 방식으로 개발을 진행

  • 스프린트 시에 프론트엔드로 참여한 팀원이 한명이여서, 프론트엔드를 프로젝트 진행 하며 학습 하는 것을 희망 하는 팀원 두명과 함께 3명(현우, 현민, 선빈)이 프론트엔드에 대해서 페어 프로그래밍으로 진행
  • 백엔드 파트는 준현, 동현이 페어 프로그래밍으로 팀을 이뤄서 진행

[BE] 퀴즈존 TDD 적용 후 퀴즈존 모듈 구성 진행

프로젝트 기획 과정에서 만들어진 Task를 기반으로 테스트 코드 작성 후 구현 진행

스크린샷 2024-11-07 오후 10.27.33.png

describe('사용자가 퀴즈존 생성 요청을 보내면 퀴즈존을 생성한다.', () => {
    it('서버는 사용자의 세션 ID를 이용하여 퀴즈존을 생성한다.', async () => {
        const sid = '1234';

        await service.create(sid);
        expect(data.hasOwnProperty(sid)).toEqual(true);
    });

    it('퀴즈존은 입장한 사용자 정보로 초기화된다.', async () => {
        const sid = '1234';
        await service.create(sid);
        expect(data[sid].player.id).toEqual(sid);
    });
    it('퀴즈존이 초기화되면 현재퀴즈번호는 0이다.', async () => {
        const sid = '1234';

        await service.create(sid);
        expect(data[sid].currentQuizIndex).toEqual(0);
    });
    it('퀴즈존이 초기화되면 문제들이 할당된다.', async () => {
        const sid = '1234';
        await service.create(sid);
        expect(data[sid].quizzes).toEqual(quizzes);
    });
    it('퀴즈존이 초기화되면 로비상태가 된다.', async () => {
        const sid = '1234';
        await service.create(sid);
        expect(data[sid].stage).toEqual('LOBBY');
    });
    it('이미 만들어진 퀴즈존을 생성하면 에러가 발생한다.', async () => {
        const sid = '1234';
        await service.create(sid);
        expect(service.create(sid)).rejects.toThrow(ConflictException);
    });
});
  • service, repository, controller 유닛테스트 진행
  • e2e 테스트 진행
    • e2e 테스트란? End-to-End 테스트의 약자로, 애플리케이션의 흐름을 처음부터 끝까지 테스트하는 방법입니다. 실제 사용자의 시나리오를 시뮬레이션하여 시스템의 모든 구성 요소가 올바르게 작동하는지 확인합니다.

[BE] WebSocket 학습과 동작 구조 분석을 위한 E2E 테스트코드 작성 시도 중

퀴즈 진행 처리를 위한 WebSocket 활용을 위해 동작 등을 e2e 테스트를 통해 확인하였습니다.

describe('PlayGateway (e2e)', () => {
    let app: INestApplication;
    let wsClient: WebSocket;
    let httpServer: any;

    beforeAll(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [PlayModule],
        }).compile();

        app = moduleFixture.createNestApplication();

        app.use(cookieParser());
        app.use(
            session({
                secret: 'my-secret',
                resave: true,
                saveUninitialized: true,
            }),
        );

        httpServer = createServer(app.getHttpAdapter().getInstance());
        app.useWebSocketAdapter(new WsAdapter(httpServer));

        await app.init();
        await app.listen(3000);
    });

    beforeEach((done) => {
        wsClient = new WebSocket('ws://localhost:8080/play');

        wsClient.on('open', () => done());
        wsClient.on('error', (err) => done(err));
    });

    afterEach(async () => {
        if (wsClient) {
            wsClient.close();
        }
    });

    afterAll(async () => {
        await app.close();
    });

    it('should connect successfully', (done) => {
        expect(wsClient.readyState).toBe(WebSocket.OPEN);
        done();
    });
});
  • [Socket.io](http://socket.io/) vs ws
    • Socket.io :
      • 그룹 브로드캐스팅 등 편의 기능들을 많이 제공하지만 성능이 떨어짐
    • ws
      • 기본적인 Websocket 기능만 제공하지만 성능이 빠름
    • QuizZone 관련 정보들을 모아 관리하고 있어 적용이 어렵지 않아 ws 활용 예정

[FE] 컴포넌트 구현 시 tsdoc 적용

공통 컴포넌트 구현 시에 컴포넌트에 대해서 빠르게 파악할 수 있도록 tsdoc을 적용한 주석을 추가하는 작업을 했습니다.

스크린샷 2024-11-07 오후 9.29.26.png

/**
 * @description
 * 자식 요소들을 스타일이 적용된 div로 감싸는  컴포넌트입니다.
 *
 * @example
 * ```tsx
 * <ContentBox>
 *   <p>이것은 ContentBox 안에 있는 내용입니다.</p>
 * </ContentBox>
 * ```
 *
 * @param {ContentBoxProps} props - ContentBox 컴포넌트의 props입니다.
 * @param {ReactNode} props.children - ContentBox 안에 감싸질 내용입니다.
 *
 * @returns {JSX.Element} 자식 요소들을 포함하는 스타일이 적용된 div를 반환합니다.
 */
const ContentBox = ({ children }: ContentBoxProps) => {
    return (
        <div className="p-4 box-border rounded-[10px] border-2 border-gray-200 flex flex-col gap-4">
            {children}
        </div>
    );
};

[FE] 2주차 공통 컴포넌트 구현 및 스토리북 적용

현우, 현민, 선빈 3명이 같이 페어 프로그래밍을 진행하여 2주차 스프린트의 태스크에 필요한 공통 컴포넌트 설계 및 구현을 진행했습니다. 공통 컴포넌트 구현 시에 작성해 둔 tsdoc과 코드를 참고하여 스토리북을 적용하였습니다. 컴포넌트 별로 스토리를 작성 했습니다.

progressBar_storybook.gif

CommonButton_Storybook.gif


[FE] 2주차 메인페이지, 퀴즈 풀이 대기 페이지, 퀴즈 풀이 페이지, 퀴즈 결과 페이지 구현

스크린샷 2024-11-07 오후 10.24.54.png

현우, 현민, 선빈 3명이 같이 페어 프로그래밍을 진행하여 2주차 스프린트의 태스크 중 퀴즈 풀이 과정의 주요 페이지에 관련된 태스크를 수행하여 4개의 페이지를 구현을 진행했습니다.

4. 트러블 슈팅

package-lock.json 이슈

  • 문제: 모노레포로 Npm을 사용할 때 의존성 충돌이 일어나는 문제

    스크린샷 2024-11-07 오후 10.29.18.png

  • 해결 과정: pnpm을 적용해서 처리 할 수 있도록 해결

    • 해결 하는 과정에서 github reverst, cherry-pick 등등 활용 해볼 수 있었음.
  • 학습한 점: github 명령어, 패키지 관리자(npm, pnpm의 차이), 모노레포, 의존성 관리


Git merge 실수

스크린샷 2024-11-07 오후 9.18.41.png

스크린샷 2024-11-07 오후 9.18.18.png

main 브랜치

main 브랜치

develop브랜치

develop브랜치

  • 문제: feature 브랜치 PR 생성 중 메인 브랜치로 머지를 해버려 문제 발생(원래는 develop에 되어야함, main배포 브랜치)
  • 해결과정
    • main브랜치에 PR된 것을 revert.
    • main브랜치가 develop브랜치 보다 앞서는 문제가 생겨 develop브랜치 헤드를 main헤드와 동기화
    • develop브랜치의 내용이 revert된 main 즉 프로젝트 세팅만 된 것으로 바뀜.
    • 다시 develop브랜치를 revert해 해결.
  • 학습한 점: 수요일에 수강 했던 호눅스님의 GIt 강의를 실전에서 바로 투입 해볼 수 있었습니다!

프론트엔드 페어 프로그래밍

  • 문제: 백엔드, 프론트엔드 비율 문제로 페어 프로그래밍을 어떻게 진행할 것인가에 대해서 생각하는 방향성이 맞지 않아서 목요일 오전 스크럼 때 관련해서 얘기를 나누면서 방향을 맞출 수 있도록 진행했습니다.
  • 해결 과정: 화요일 부터 3일간 페어 프로그래밍, 설계 방향성을 논의 하며 백엔드 팀원들이 프론트엔드 컴포넌트를 직접 구현해보는 시간을 가지면서, 프론트엔드에 대한 작업을 함께 할 수 있는 지 체크할 수 있도록 했습니다.
  • 학습한 점: react, component 설계, tailwind, shadcn UI

nest cli 프로젝트 세팅 시에 .git 생성 되는 문제

  • 문제: nest cli로 nest 프로젝트 환경 생성을 하게 되면 기본으로 .git이 생기는 문제가 있었음. 모노레포 환경으로 프로젝트를 진행해야 되기 때문에 서브 모듈이 생기는 문제를 해결 해야 했습니다.
    • 서브 모듈: Git 저장소 안에 다른 Git 저장소를 포함시키는 기능으로, 여러 프로젝트를 하나의 프로젝트처럼 사용할 수 있게 해줍니다. 하지만 이 경우 모노레포 구조와 충돌할 수 있어 문제가 되었습니다.
  • 해결 과정: 이를 해결하기 위해서 —-skip-git 옵션을 추가하여 해결 하였습니다.
  • 학습한 점: 모노레포, 서브모듈

Vite build 시 tsconfig.json 이슈

  • 문제: vite + react + typescript 프로젝트 환경에서 빌드가 정상적으로 되는 지 확인 하기 위해 시도 중에 에러가 발생했습니다.

    스크린샷 2024-11-07 오후 9.51.29.png

    compositenoEmit 옵션으로 인해서 에러가 발생하는 상황이 있었습니다.

  • 해결 과정: 이를 해결하기 위해 tsconfig.json , tsconfig.app.json, tsconfig.node.json 옵션을 비교하면서 확인을 했습니다. 확인을 해보니 위의 옵션이 문제가 아닌 include 옵션이 tsconifg.json에 중복해서 있는 것을 확인하여 이를 제거하니 정상적으로 빌드가 진행되어 졌습니다.

  • 학습한 점: vite build, tsconfig 옵션


Husky comitlint.config.js 적용 시 파일 에러

[type:module로 설정 했을 때 생긴 문제](https://www.notion.so/type-module-135f1897cdf58055a5a0ef05ab8d151a?pvs=21)

  • 문제: comitlint 파일을 적용했는데 계속해서 원인불명의 에러가 발생
  • 해결 과정: 프로젝트를 진행하면서 팀원들이 공통된 commit convention을 지키기 위해서, husky를 적용했습니다. 그 과정에서 type: module 을 적용해둬서 commitlint.config.js 파일이 제대로 적용이 안되는 문제가 생겼습니다. 이를 해결하기 위해서 commitlint.config.cjs 로 수정하여 해결 했습니다.

5. 배포 및 시연

첫 주차 배포를 목표로 하였으나, 개발환경 구성 및 구현 이슈와 페어프로그래밍 등으로 인한 일정 연기로 일정을 변경하게 되었습니다.

  • 프론트엔드 스토리북, 구현한 페이지 시연

6. 다음주 목표

7. 질의응답

https://docs.google.com/presentation/d/1ULseBdsbrd9HlR1jyvu2ZjzFfIqczHIwS8yuTKsGg6Y/edit#slide=id.g3159c74158f_0_0

3주차 발표 자료

원본 링크: https://jacky0831.notion.site/3-12cf1897cdf5800c9aede6413dd72ab6?pvs=4

발표자료
## 1. 프로젝트 개요

https://github.com/boostcampwm-2024/web08-BooQuiz

  • 프로젝트명: BooQuiz
  • 목적: 300명의 부스트캠퍼를 감당할 수 있는 실시간 퀴즈 플랫폼

2. 기술 스택

  • 프론트엔드: React, Vite, Tailwind, Shadcn UI, Typescript
  • 백엔드: Nest.js, Typescript
  • 공통: webSocket

3. 이번주 핵심 작업 내용

[공통] 모각글 활동

토요일 오전 ☀️ 가볍게 모여서 글을 작성하는 시간을 가졌습니다.

[FE] 퀴즈존 관리 퍼널 커스텀 훅 구현

복잡한 퀴즈 진행 과정을 퍼널 개념을 적용해서 중앙화된 상태관리와 로직으로 관리할 수 있도록 했습니다.

고민사항

  • 퀴즈 진행 과정의 복잡한 상태 관리 처리

스크린샷 2024-11-14 오전 11.12.38.png

  • 참고 자료

[토스ㅣSLASH 23 - 퍼널: 쏟아지는 페이지 한 방에 관리하기](https://youtu.be/NwLWX2RNVcw?si=Vs66RN8erFHPi6Ft)

퍼널 패턴이란?

퍼널(Funnel)은 '깔때기'라는 뜻으로, 사용자가 특정 목표를 달성하기까지의 단계를 위에서 아래로 시각화했을 때 깔때기 모양이 되는 것에서 유래했습니다. 주로 마케팅에서 사용되던 이 개념을 개발에 적용한 것이 퍼널 패턴입니다.

퍼널 패턴의 특징

  1. 단방향성: 사용자는 정해진 순서대로 단계를 진행
  2. 단계별 데이터 관리: 각 단계마다 필요한 데이터를 독립적으로 관리
  3. 상태 추적: 현재 진행 단계와 이전 단계들의 이력 관리
  4. 검증: 각 단계 진입 시 필요한 조건 검증

BooQuiz의 퍼널 패턴 구현

1. QuizZone 상태 관리 퍼널

현재 구현된 QuizZone은 다음과 같은 퍼널 구조를 가집니다:

graph TD
    A[LOBBY] --> B[QUIZ_PROGRESS]
    B --> C[RESULT]
    
    subgraph QUIZ_PROGRESS State
        D[WAITING]
        E[IN_PROGRESS]
        F[COMPLETED]
        
        D --> E
        E --> F
        F --> |Next Quiz|D
    end
    
    B === D
    F --> |Last Quiz|C
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#f0f0f0,stroke:#333,stroke-width:2px
    style C fill:#9ff,stroke:#333,stroke-width:2px
    style D fill:#fff,stroke:#666
    style E fill:#fff,stroke:#666
    style F fill:#fff,stroke:#666
    
    classDef subgraphStyle fill:#f0f0f0,stroke:#333,stroke-width:1px
    class QUIZ_PROGRESS subgraphStyle
Loading

2. 상태 관리 구현

퍼널 내부 상태에 따라서 퀴즈존 상태가 관리되도록 구현하였습니다.

// 메인 퍼널 상태
type QuizZone = 'LOBBY' | 'QUIZ_PROGRESS' | 'RESULT';

// 서브 퍼널 상태 (QUIZ_PROGRESS 내부)
type SolveStage = 'WAITING' | 'IN_PROGRESS' | 'COMPLETED';

// 각 단계별 데이터 구조
interface QuizZoneData {
    Lobby: {
        participants: number;
        totalQuizCount: number;
        isHost: boolean;
        quizTitle: string;
        description?: string;
    };
    quizProgress: {
        currentQuiz: {
            question: string;
            timeLimit: number;
            type?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER';
            // ... 기타 퀴즈 관련 데이터
        };
        progress: QuizProgress;
    };
    result: {
        score: number;
        quizzes: any;
        submits: any;
    };
}

3. 단계별 상태 전환 관리

퍼널 패턴의 핵심인 단계 전환은 다음과 같이 구현되어 있습니다:

// "대기실", "퀴즈 진행", "퀴즈 결과" 상태 관리
function changeMainStage(stage: QuizZone, data?: any) {
    setIsTransitioning(true);
    try {
        setQuizZone(stage);
        if (stage === 'QUIZ_PROGRESS') {
            setSolveStage('WAITING');
            prepareTimer.start();
            solutionTimer.stop();
        }
        // ... 데이터 업데이트 로직
    } finally {
        setIsTransitioning(false);
    }
}

// "퀴즈 대기" "퀴즈 풀이 중" "퀴즈 풀이 후 대기"
function handleQuizCycle(stage: SolveStage, data?: any) {
    setIsTransitioning(true);
    try {
        setSolveStage(stage);
        if (stage === 'WAITING') {
            solutionTimer.stop();
        } else if (stage === 'IN_PROGRESS') {
            prepareTimer.stop();
            solutionTimer.start();
        }
        // ... 단계별 처리 로직
    } finally {
        setIsTransitioning(false);
    }
}

퍼널 패턴 적용의 이점

  • 메인 스테이지와 서브 스테이지의 분리로 복잡한 상태 관리를 단순화
  • 각 단계별 명확한 데이터 구조 정의
  • 상태 전환의 안정성 확보
flowchart TD
    subgraph Complex["기존 상태관리 방식"]
        direction TB
        Store[메인 스토어/페이지]
        
        Store --> Lobby[로비 스토어/페이지]
        Store --> Quiz[퀴즈 스토어/페이지]
        Store --> Result[결과 스토어/페이지]
        
        Lobby <-.-> |상태 동기화| Quiz
        Quiz <-.-> |상태 동기화| Result
        Lobby <-.-> |상태 공유| Result
        
        Lobby --> LobbyP[참가자 상태]
        Lobby --> LobbyH[호스트 상태]
        
        Quiz --> QuizQ[문제 상태]
        Quiz --> QuizT[타이머 상태]
        Quiz --> QuizS[제출 상태]
        
        Result --> ResultS[결과 상태]
        Result --> ResultSt[통계 상태]
        
        style Store stroke-dasharray: 5 5
        style Lobby stroke-dasharray: 5 5
        style Quiz stroke-dasharray: 5 5
        style Result stroke-dasharray: 5 5
    end
    
    Complex --> Simple
    
    subgraph Simple["퍼널 패턴 적용"]
        direction TB
        Manager[QuizZoneManager]
        
        Manager --> States[퍼널 상태]
        Manager --> Data[스테이지 데이터]
        
        States --> MainStates[메인 스테이지]
        States --> SubStates[서브 스테이지]
        
        MainStates --> MS1[LOBBY]
        MainStates --> MS2[QUIZ_PROGRESS]
        MainStates --> MS3[RESULT]
        
        SubStates --> SS1[WAITING]
        SubStates --> SS2[IN_PROGRESS]
        SubStates --> SS3[COMPLETED]
        
        Data --> D1[Lobby 데이터]
        Data --> D2[Quiz 데이터]
        Data --> D3[Result 데이터]
        
        style Manager fill:#f9f9f9
        style States fill:#e6f3ff
        style Data fill:#e6ffe6
    end
    
    classDef stateNode fill:#fff,stroke:#666,stroke-width:2px
    classDef dataNode fill:#f0f0f0,stroke:#999
    class MS1,MS2,MS3,SS1,SS2,SS3 stateNode
    class D1,D2,D3 dataNode
Loading

향후 개선 방향

1. 타입 안정성 강화

  • 각 단계별 데이터 타입 더 명확히 정의
  • 단계 전환 시 필요한 데이터 검증 강화

2. 에러 처리 개선

  • 각 단계별 에러 상황 정의
  • 복구 전략 수립
  • 사용자 피드백 메커니즘 강화

3. 단계별 로딩 상태 관리

  • 전환 과정의 시각적 피드백 개선
  • 비동기 작업의 안정성 확보

퀴즈존 상태 관리에 퍼널 패턴 도입 결과

  1. 복잡한 상태 관리를 단순하고 예측 가능한 흐름으로 변환
  2. WebSocket 이벤트 처리를 상태 중심의 명확한 구조로 정리
  3. 타이머 관리를 자동화하여 동기화 문제 해결
  4. 데이터 구조를 체계화하여 유지보수성 향상

[BE ↔ FE] WebSocket 기반 서버 클라이언트 통신 구현

ws모듈로 WebSocket 양방향 통신을 구현

  • [웹소켓 메시지 명세서](https://www.notion.so/ae6f257bb1694161b4f26ca8c2fe8d3a?pvs=21)

  • 프론트엔드 예시 - 웹소켓 연결 예시

      const ws = new WebSocket('ws://localhost:3000/play');
      setWs(ws);
    
      ws.onopen = () => {
          console.log('연결됨');
      };
  • 백엔드 예시 - 퀴즈 시작 메시지 수신 및 응답

        @SubscribeMessage('start')
        async start(@ConnectedSocket() client: WebSocket) {
            this.server.emit('nextQuiz', client);
            return {
                event: 'start',
                data: 'OK',
            };
        }

[BE] 퀴즈 풀이 이벤트 기반 서비스 로직

서버의 퀴즈 진행의 느슨한 결합을 만들기 위해 이벤트 기반 아키텍처를 채택

  • 퀴즈 진행 이벤트 흐름

    sequenceDiagram
        participant Client
        participant Gateway
        participant QuizStore
        participant EventEmitter
    
        Note over Client,EventEmitter: 1. 퀴즈존 생성 및 초기화
        Client->>Gateway: POST /quiz-zone (퀴즈존 생성)
        Gateway->>QuizStore: 퀴즈존 세션 저장
        Gateway->>Client: 201 Success (퀴즈존 ID)
        
        Note over Client,EventEmitter: 2. 초기 데이터 로딩
        Client->>Gateway: GET /quiz-zone/{id}
        Gateway->>QuizStore: 퀴즈존 데이터 조회
        Gateway->>Client: 200 Success (퀴즈존 초기 데이터)
        
        Note over Client,EventEmitter: 3. 웹소켓 연결 및 게임 시작
        Client->>Gateway: WebSocket 연결 요청 (/ws/play)
        Gateway-->>Client: 연결 수립
        Client->>Gateway: event:start 발생
        Gateway->>QuizStore: 게임 상태 업데이트
        Gateway-->>Client: data: OK
        
        Note over Client,EventEmitter: 4. 퀴즈 진행
        Gateway->>EventEmitter: timeoutInterval 시작
        EventEmitter-->>Gateway: nextQuiz 이벤트
        Gateway-->>Client: 다음 문제 데이터 전송
        
        Note over Client,EventEmitter: 5. 답안 제출 또는 타임아웃
        alt 답안 제출
            Client->>Gateway: submit 이벤트 {index, answer, submittedAt}
            Gateway->>QuizStore: 답안 검증 및 저장
            Gateway-->>Client: 제출 완료 응답
        else 시간 초과
            EventEmitter->>Gateway: quizTimeout 이벤트
            Gateway->>QuizStore: 타임아웃 상태 저장
            Gateway-->>Client: quizTimeout 알림
        end
        
        Note over Client,EventEmitter: 6. 다음 문제 또는 퀴즈 종료
        alt 다음 문제 있음
            EventEmitter->>Gateway: nextQuiz 이벤트
            Gateway->>QuizStore: 다음 문제 상태 업데이트
            Gateway-->>Client: 다음 문제 데이터
        else 마지막 문제 종료
            alt 답안 제출 또는 타임아웃
                Gateway->>QuizStore: 최종 상태 확인
                Gateway-->>Client: finish 이벤트
                Gateway-->>Client: summary 이벤트 (점수, 제출이력, 문제정보)
            end
        end
        
        Note over Gateway,QuizStore: Store 데이터
        Note right of QuizStore: - stage\n- question\n- currentIndex\n- playTime\n- startTime\n- deliverTime
    
    Loading
  • 코드 예시 - PlayGateway - 퀴즈 진행 처리

    • 웹소켓 게이트웨이 초기화시 이벤트 리스너 등록(server.on)
    • 필요한 부분에서 server.emit을 통해 이벤트 발생 및 처리
    @WebSocketGateway({ path: '/play' })
    export class PlayGateway implements OnGatewayConnection, OnGatewayInit {
        @WebSocketServer()
        server: Server;
    
        constructor(
            @Inject('PlayInfoStorage')
            private readonly plays: Map<WebSocket, PlayInfo>,
            private readonly playService: PlayService,
        ) {}
    
        /**
         * WebSocket 서버 초기화 시, 퀴즈 진행 및 요약 이벤트를 처리하는 핸들러를 설정합니다.
         *
         * @param server - WebSocket 서버 인스턴스
         */
        afterInit(server: Server) {
            server.on('nextQuiz', (client: WebSocket) => this.playNextQuiz(client));
            server.on('summary', (client: WebSocket) => this.summary(client));
    
        }
        
        /**
         * 다음 퀴즈를 시작하고 클라이언트에 전달합니다.
         *
         * @param client - WebSocket 클라이언트
         */
        private async playNextQuiz(client: WebSocket) {
            try {
                const playInfo = this.getPlayInfo(client);
                const { quizZoneId } = playInfo;
    
                const { intervalTime, nextQuiz } = await this.playService.playNextQuiz(quizZoneId);
    
                client.send(JSON.stringify({ event: 'nextQuiz', data: nextQuiz }));
    
                playInfo.submitHandle = setTimeout(() => {
                    this.quizTimeOut(client);
                }, intervalTime + nextQuiz.playTime);
            } catch (error) {
                if (error instanceof NotFoundException) {
                    client.send(JSON.stringify({ event: 'finish' }));
                    this.server.emit('summary', client);
                }
            }
        }
    }

[BE] tsdoc, swagger, Websocket 명세서

프론트엔드, 백엔드 협업을 위해 코드 문서화

  • swagger

  • tsdoc

  • [웹소켓 메시지 명세서](https://www.notion.so/ae6f257bb1694161b4f26ca8c2fe8d3a?pvs=21)

  • swagger예시

        @Post()
        @HttpCode(201)
        @ApiOperation({ summary: '새로운 퀴즈존 생성' })
        @ApiResponse({ status: 201, description: '퀴즈존이 성공적으로 생성되었습니다.' })
        @ApiResponse({ status: 400, description: '세션 정보가 없습니다.' })
        async create(@Session() session: Record<string, any>): Promise<void> {
            const sessionId = session.id;
    
            if (sessionId === undefined) {
                throw new BadRequestException('세션 정보가 없습니다.');
            }
    
            await this.quizZoneService.create(sessionId);
        }
  • tsdoc예시

        /**
         * 퀴즈 게임을 시작하는 메시지를 클라이언트로 전송합니다.
         *
         * @param client - WebSocket 클라이언트
         * @returns {Promise<SendEventMessage<string>>} 퀴즈 시작 메시지
         */
        @SubscribeMessage('start')
        async start(@ConnectedSocket() client: WebSocket): Promise<SendEventMessage<string>> {
            this.server.emit('nextQuiz', client);
            return {
                event: 'start',
                data: 'OK',
            };
        }
  • FE: https://boostcampwm-2024.github.io/web08-BooQuiz/docs/frontend

  • BE: https://boostcampwm-2024.github.io/web08-BooQuiz/docs/backend


WebSocket 명세서 예시: 클라이언트 → 서버 메시지

  1. start 이벤트
  • 용도: 클라이언트가 퀴즈를 시작하고 싶을 때 사용.

  • 송신 데이터: 없음

    {
      "event": "start",
    }
  • 서버 응답 데이터:

    {
      "event": "start",
      "data": "OK"
    }

WebSocket 명세서 예시: 서버 → 클라이언트 메시지

  1. nextQuiz 이벤트
  • 용도: 새로운 퀴즈를 클라이언트에 전달.

  • 송신 데이터: 다음 퀴즈 정보 (nextQuiz)

    interface CurrentQuizDto {
        readonly question: string;
        readonly stage: 'LOBBY' | 'WAITING' | 'IN_PROGRESS' | 'COMPLETED' | 'RESULT';
        readonly currentIndex: number;
        readonly startTime: number;
        readonly deadlineTime: number;
    }
    {
      "event": "nextQuiz",
      "data": {
          stage: "WAITING",
          question: "질문1",
          currentIndex: 0,
          playTime: 3000,
          startTime: 172392390,
          deadlineTime: 172395390,
       }
    }

[인프라] github page에 tsdoc, Storybook 배포

프로젝트 개발 과정에서 작성되는 TSDoc과 Storybook을 GitHub Pages를 통해 배포, 팀원들이 실시간으로 문서화된 컴포넌트와 API를 확인


[인프라] github action으로 테스트 및 배포 자동화

Develop와 main 브랜치에 PR이 올라가면 pnpm test 명령어 실행을 통해 테스트 스텝을 거치게 설정

  • 테스트 자동화
    • develop 브렌치에 PR을 올리면 프론트앤드 테스트와 백앤드 테스트가 병렬적으로 돌아가도록 github action을 구현

DevOps 아키텍처

DevOps 아키텍처


[인프라] nginx 설정

Nginx 리버스 프록시 설정

  • 번들링된 정적파일 웹서버(nginx)에서 서빙되도록 설정
  • ssl 인증서를 발급하여 https 통신 하도록 설정.
  • /api/endpoint로 들어온 요청에 대해 was로 /endpoint 요청을 보냄

[SSL 인증서 발급 및 HTTPS 적용하기](https://www.notion.so/SSL-HTTPS-6e64c76c8ccd4965b4d060864c8a96a7?pvs=21)


4. 트러블 슈팅

[BE] http, webSocket 통신 사이에 session Id 차이

  • 문제

    • 퀴즈존을 생성하고 퀴즈를 진행하려하니 서버가 해당 클라이언트의 퀴즈존을 찾지 못하는 현상 발생
  • 원인

    • API로 세션 ID를 이용해 퀴즈존을 생성
    • WS 연결할 때 세션 ID를 이용해 퀴즈존을 탐색
    • 퀴즈존은 API를 통해 넘어오는 세션 ID 기준으로 생성되어 cookie-parser 를 활용할 수 있음.
    • WS를 연결을 시작할때는 cookie-parser 활용할 수 없음.
    • WS에서 직접 파싱한 세션 IDcookie-parser 와 일치하지 않는 문제 발생!
  • 해결 과정

    • WebSocket 연결할 때 세션 ID s:{sessionId}.23153215 와 같은 형태로 들어오는 것을 발견
    • express-session 라이브러리는 쿠키에 세션 ID를 저장할 때 s:{sessionID}.{timestamp} 같은 형식을 사용해 세션을 구성
    • WS의 sessionId 파싱 로직을 cookie-parsor 의 결과와 일치하도록 수정하여 문제 해결
    const quizZoneId = cookies['connect.sid'].split('.').at(0).slice(2);

[인프라] GitHub Actions 배포 중 데이터 전송 오류 해결 과정

sequenceDiagram
    participant GH as GitHub 저장소
    participant GA as GitHub Actions
    participant PS as Public Server
    participant PV as Private Server

    Note over GH,PV: 초기 배포 프로세스
    
    GH->>GA: 코드 푸시 트리거
    
    rect rgb(200, 220, 250)
        Note over GA: 빌드 프로세스
        GA->>GA: 애플리케이션 빌드
        GA->>GA: 배포 파일 생성
        GA->>GA: SSH Agent Key 추가
    end
    
    rect rgb(220, 240, 220)
        Note over GA,PS: 1차 전송
        GA->>PS: SSH Agent 적용 바탕으로 연결
        GA->>PS: SCP로 빌드 파일 전송
    end
    
    rect rgb(240, 220, 220)
        Note over PS,PV: 2차 전송
        PS->>PV: SSH 연결
        PS->>PV: SCP로 빌드 파일 전송
        PV->>PV: 애플리케이션 배포
    end
    
    Note over PV: 애플리케이션 실행 중
Loading
  • 처음에는 github action에서 빌드된 파일을 ssh와 scp 명령어를 이용하여 public 서버로 전달하고, 똑같은 방식으로 public 서버에서 private 서버로 전달해서 서버를 띄우는 방식을 시도해보았습니다.
  • 이 과정에서 데이터 전송에 관련한 오류를 겪었는데, 이것은 쉘 스크립트를 활용하여 해결했습니다.

[인프라] 배포 환경 구축 시 직면한 제약사항과 Docker 도입

DevOps 아키텍처

DevOps 아키텍처

하지만 빌드된 파일을 바탕으로 실행을 시키려 했으나, private subnet 환경에서 node, pm2와 같은 외부 의존성 파일들을 외부로부터 설치해주기 위해 NAT GATEWAY를 사용해야 한다는 것을 나중에 깨달았습니다. 따라서 NAT GATEWAY 설정을 추가적으로 해줬습니다.

web server는 CentOS를 사용 중이었는데, 개발 환경보다 낮은 노드 버전만을 지원했습니다. 그리고 CentOS에서는 Vite에서 요구하는 최소한의 node 버전도 지원하지 못했습니다. 이를 맞추기 위해 Docker를 적용해서 배포할 수 있도록 작업을 진행하였습니다.

배포를 위한 무수한 시도들

배포를 위한 무수한 시도들



5. 배포 및 시연

지난 주에 연기되었던 2주차 목표인 1인이 단독으로 퀴즈존을 생성해서 퀴즈를 진행할 수 있도록 한다 까지 완료해서 배포를 진행하였습니다.


6. 다음주 목표

  • 10명 이상을 하나의 퀴즈존에 받아서 퀴즈 진행하기

7. 질의응답

4주차

원본 링크: https://jacky0831.notion.site/4-12cf1897cdf580b3bdc1f5e22505b587?pvs=4

발표자료
# Web08

Team - 하하

Project - BooQuiz

1. 프로젝트 개요

https://github.com/boostcampwm-2024/web08-BooQuiz

  • 프로젝트명: BooQuiz
  • 목적: 300명의 부스트캠퍼를 감당할 수 있는 실시간 퀴즈 플랫폼

2. 기술 스택

  • 프론트엔드: React, Vite, Tailwind, Shadcn UI, Typescript
  • 백엔드: Nest.js, Typescript
  • 공통: webSocket

3. 이번주 핵심 작업 내용

[공통] 모각글 활동

토요일 오전 ☀️ 가볍게 모여서 글을 작성하는 시간을 가졌습니다.

[WebSocket 통신 난독화](https://www.notion.so/WebSocket-72c27e4fb24f4473be05cc2238b1781a?pvs=21)

[[FE] Vitest 테스트 코드 작성하기](https://www.notion.so/FE-Vitest-140f1897cdf5801587c5cb35829becda?pvs=21)

[프론트엔드에서 테스트 코드를 작성해야 되는 이유](https://www.notion.so/a0aaa7cc860445a49e000a58f4c11e57?pvs=21)

[도커를 이용한 테스트 환경 구축](https://www.notion.so/a4e628e3a2664f10bc5ccd3915e02436?pvs=21)

[웹소켓 + 리버스 프록시(엔진엑스)](https://www.notion.so/2325bd00d5b04718b295857ff2dbfcef?pvs=21)

[WsAdapter 커스터마이징](https://www.notion.so/WsAdapter-b702ace55d4c46be9c9bb97df1c50652?pvs=21)


[인프라] 테스트 서버 환경 세팅

한정된 자원과 작은 서비스 규모를 고려하여, 테스트 서버를 Docker를 활용해 구축

[도커를 이용한 테스트 환경 구축](https://www.notion.so/a4e628e3a2664f10bc5ccd3915e02436?pvs=21)

1. 개요

CI/CD 환경 구축 완료 후 실제 배포 과정에서 발생한 이슈들을 효과적으로 해결하기 위해 Docker 기반 테스트 환경을 구축했습니다. 이는 변경사항을 변경사항 브랜치developmain 순으로 머지하기 전에 검증할 수 있는 격리된 환경으로, 실제 서비스에 영향을 주지 않으면서도 운영 환경과 동일한 조건에서 테스트할 수 있습니다.

2. 선택 이유

데모데이 배포 과정에서 발생한 여러 이슈들로 인해 안전한 테스트 환경의 필요성이 대두되었습니다. 특히 변경사항을 실제 배포 환경에 적용하기 전에 검증할 수 있는 환경이 필요했습니다. Docker를 선택한 주된 이유는 한정된 자원과 작은 서비스 규모를 고려한 리소스 효율성과 경제성입니다. 단일 공인 IP와 서브도메인을 활용하여 운영/테스트 환경을 효과적으로 분리하면서도 비용을 최소화할 수 있었습니다.

3. 대안 분석

세 가지 주요 옵션을 검토했습니다:

  1. TestContainers: NestJS의 E2E 테스트용 라이브러리로, 동적으로 테스트 환경을 생성하고 삭제하는 방식이지만 실제 배포 환경 검증이 어렵다는 한계가 있었습니다.
  2. 별도의 스테이징 환경: 완벽한 운영 환경 검증이 가능하지만 현재 프로젝트 규모에서는 비용 부담이 큽니다.
  3. Docker 기반 통합 환경: 샌드박스와 스테이징 환경의 장점을 결합하여 비용 효율적인 테스트가 가능한 선택이었습니다.

4. 장단점 분석

장점으로는 운영 환경과 동일한 구성으로 정확한 테스트가 가능하며, Public Subnet의 nginx 서버를 재활용하여 비용을 절감할 수 있다는 점입니다. 단일 IP와 서브도메인 활용으로 환경 분리도 효과적입니다. 단점으로는 Docker 리소스 관리가 추가로 필요하고 nginx 프록시 설정의 복잡도가 증가한다는 점이 있습니다.

5. 기술적 고려사항

성능과 확장성을 고려하여 Docker 컨테이너의 리소스 제한을 설정하고, nginx 프록시 설정을 최적화했습니다. Public Subnet의 nginx 서버가 테스트 서버로 요청을 프록시하는 방식을 채택하여 효율적인 요청 처리가 가능하도록 구성했습니다. 서브도메인을 통한 환경 분리로 향후 확장 가능성도 고려했습니다.


[BE] ws에서의 사용자 식별 문제 처리

ws 모듈은 기본적으로 방 관리와 브로드캐스팅을 지원하지 않기 때문에 저희가 직접 방 관리를 하기 위해 설계 하였습니다.

handleConnection(client: WebSocket) {
	const cookies = parse(request.headers.cookie);
  const sessionId = cookies['connect.sid'].split('.').at(0).slice(2);
  client['sessionId'] = sessionId; -> 이런식으로 **재연결 시에도 고유성 보장**
}
  • 문제: 퀴즈를 진행할 때 웹소켓이 끊기는 생각보다 많았습니다. 웹소켓이 재연결될 때마다 소켓 정보가 바뀌어서 재연결 시에 동일한 사용자로 판단할 수 없는 경우가 발생하였습니다.
  • 해결: 연결 시에 통신을 시작시키는 handleConnection 함수 안에서 쿠키에서 세션을 파싱한 뒤, sessionId를 소켓 객체 안에 key-value로 추가하여 클라이언트에서 전달하는 웹소켓에서 필요할 때 마다 sessionId를 추출하여 사용자를 식별하도록 진행하였습니다.

[BE] 다수 인원 이용 가능하게 퀴즈존 생성

1명의 사용자만 이용하는 퀴즈에서 10명이 이용할 수 있는 퀴즈존 생성

const PlayInfo = {
	submitHandle?: NodeJS.Timeout; // 해당 퀴즈존의 타이머
	quizZoneClients: Map<sessionId, WebSocket>; // 해당 퀴즈존의 소켓 정보
	hostSocket: WebSocket // 방장 소켓 따로 관리
}

const plays =  Map<String, PlayInfo>,
  • 문제: Socket.io와 달리 ws에는 방관리, broadcast 를 지원하지 않아 해당 로직들을 처리 하기 위해서 퀴즈존 안에 사용자들의 소켓 정보를 가지고 있어야 했습니다.

  • 해결: 문제를 해결하기 위해 quizZoneId를 key로 설정하여 value 값으로 방 안에 소켓 정보들과 게임에 필요한 정보들을 담아두어 방 관리를 할 수 있도록 설계하였습니다.

    → 서버에서 내부적으로 웹소켓 연결과 사용자 정보를 관리 할 수 있는plays 저장소 추가

  • 퀴즈존마다 이벤트를 등록해서, 주기(사용자가 등록한 퀴즈 플레이 시간)마다 해당 퀴즈존의 소켓에게 렌더링에 필요한 정보 등을 메세지로 보내는 로직을 구현했습니다.


[공통] 재연결 문제 처리

웹소켓이 끊기는 경우가 다수 발생하여 재연결 시에 바로 해당 퀴즈 진행 상태로 복구되도록 처리

  • 문제: 웹소켓 연결이 끊기면 현재 문제가 끝났다는 이벤트를 주기 전까지 퀴즈 풀이를 진행할 수 없는 문제가 있었습니다.
  • 해결: 연결이 끊김을 감지한 클라이언트가 REST API 요청을 다시 보내도록 하였고, 서버는 해당 퀴즈존의 진행 상태, 시작 시간, 끝나는 시간 등을 건네 주도록 하였습니다. 이 응답으로 프론트엔드에서 뷰를 재랜더링 하여 사용자 UX를 향상시켰습니다.

[BE] 예외 케이스에 대한 고려

퀴즈 진행 상태, 퀴즈존에 등록된 사용자 여부에 따라 예외처리

  • 퀴즈가 진행 중 일때 입장 안 했던 사용자가 입장할 경우
  • 퀴즈존에 등록되지 않은 사람이 제출, 시작 등을 요청할 경우

[BE] 윈스턴 로거

운영 상황 파악을 위한 로거 추가

  • 로깅은 서비스 운영에 도움을 주는 요소로 필수가 아니라 판단하여 첫 배포에서는 포함하지 않음
  • 1인 퀴즈 진행에서 10명 사용자 퀴즈 진행으로 기능 확장에 따라 성능 및 에러 모니터링을 위한 윈스턴 로거 등록
  • 이후 성능 테스트 등 에서 활용할 예정

[FE] 퍼널 리팩토링

퍼널 패턴이란?

퍼널(Funnel)은 '깔때기'라는 뜻으로, 사용자가 특정 목표를 달성하기까지의 단계를 위에서 아래로 시각화했을 때 깔때기 모양이 되는 것에서 유래했습니다. 주로 마케팅에서 사용되던 이 개념을 개발에 적용한 것이 퍼널 패턴입니다.

  • 참고 자료

[토스ㅣSLASH 23 - 퍼널: 쏟아지는 페이지 한 방에 관리하기](https://youtu.be/NwLWX2RNVcw?si=Vs66RN8erFHPi6Ft)

퍼널 패턴의 특징

  1. 단방향성: 사용자는 정해진 순서대로 단계를 진행
  2. 단계별 데이터 관리: 각 단계마다 필요한 데이터를 독립적으로 관리
  3. 상태 추적: 현재 진행 단계와 이전 단계들의 이력 관리
  4. 검증: 각 단계 진입 시 필요한 조건 검증

스크린샷 2024-11-14 오전 11.12.38.png

현재 구현된 QuizZone은 다음과 같은 퍼널 구조를 가집니다

graph TD
    A[LOBBY] --> B[QUIZ_PROGRESS]
    B --> C[RESULT]
    
    subgraph QUIZ_PROGRESS State
        D[WAITING]
        E[IN_PROGRESS]
        F[COMPLETED]
        
        D --> E
        E --> F
        F --> |Next Quiz|D
    end
    
    B === D
    F --> |Last Quiz|C
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#f0f0f0,stroke:#333,stroke-width:2px
    style C fill:#9ff,stroke:#333,stroke-width:2px
    style D fill:#fff,stroke:#666
    style E fill:#fff,stroke:#666
    style F fill:#fff,stroke:#666
    
    classDef subgraphStyle fill:#f0f0f0,stroke:#333,stroke-width:1px
    class QUIZ_PROGRESS subgraphStyle
Loading

1. 리팩토링 배경

1.1 초기 개발 상황

3주차에 진행했던 "1인 단독 퀴즈 풀이 기능" 개발은 3주차 데모데이 시연이라는 시간적 제약 속에서 이루어졌습니다. 퀴즈존의 상태 관리를 위해 퍼널 패턴을 적용한 커스텀 훅을 구현했는데, 이는 사용자가 퀴즈를 풀어나가는 과정을 단계별로 관리하기 위한 것이었습니다.

하지만 빠르게 백엔드와 연동하여 시연 가능한 상태를 만드는 데 초점을 맞추다 보니, 코드의 품질이나 구조에 대한 깊은 고민을 할 시간이 부족했습니다. 특히 퍼널 패턴을 통한 상태 관리와 실시간 서버 통신을 연결하는 과정에서 많은 임시방편적인 코드가 작성되었습니다. 이러한 상황에서 만들어진 코드는 기능적으로는 동작했지만, 여러 가지 구조적인 문제점을 내포하고 있었습니다.

1.2 기존 코드의 문제점

1.2.1 상태 관리의 복잡성

기존 코드에서는 퀴즈존의 여러 상태들이 독립적인 useState 훅으로 관리되고 있었습니다. 예를 들어, 퀴즈의 진행 단계, 풀이 상태, 진행 상황 등이 각각 별도의 상태로 관리되었습니다. 이러한 구조는 상태 간의 연관관계를 파악하기 어렵게 만들었고, 하나의 상태 변경이 다른 상태에 미치는 영향을 추적하기 힘들게 만들었습니다.

// 이전 코드의 복잡한 상태 관리 예시
const [quizZone, setQuizZone] = useState<QuizZone>('LOBBY');
const [solveStage, setSolveStage] = useState<SolveStage>('WAITING');
const [quizProgress, setQuizProgress] = useState<QuizProgress>({...});
const [quizZoneData, setQuizZoneData] = useState<Partial<QuizZoneData>>({});

특히 상태를 변경하는 함수를 작성할 때마다 여러 상태들의 정합성을 일일이 확인해야 하는 번거로움이 있었습니다. 또한 서버에서 보내는 메시지를 통한 퀴즈존 상태 동기화가 설계되어 있음에도 불구하고, 상태 변화에 따른 화면 전환이 프론트엔드 로직에 과도하게 의존적인 문제가 있었습니다.

1.2.2 비즈니스 로직의 분산

퀴즈 진행에 관한 비즈니스 로직이 여러 함수에 걸쳐 분산되어 있었습니다. 메인 스테이지 변경, 퀴즈 사이클 관리, 다음 문제로의 진행 등의 로직이 각각 다른 함수로 구현되어 있어, 전체적인 흐름을 파악하기 어려웠습니다.

// 분산된 비즈니스 로직 예시
function changeMainStage(stage: QuizZone, data?: any) {...}
function handleQuizCycle(stage: SolveStage, data?: any) {...}
function proceedToNextQuiz() {...}

또한 비즈니스 로직이 변경될 때마다 여러 함수를 동시에 수정해야 하는 상황이 발생했고, 실제 데이터를 조회, 전처리, 노출하는 처리들이 다른 처리들과 섞여있거나 파편화되어 있어 로직의 응집도가 낮고 유지보수가 어려운 구조였습니다.

1.2.3 타이머 의존성 문제

타이머 관리를 위한 useTimer 커스텀 훅이 퍼널 훅 내부에 직접 구현되어 있었습니다. 이로 인해 타이머의 상태 변화가 퍼널의 전체 상태에 직접적인 영향을 미치는 구조가 되었습니다. 타이머 로직과 퍼널 로직이 강하게 결합되어 있어, 타이머 동작을 수정하거나 테스트하기가 어려웠고, 퍼널 로직의 수정도 타이머 로직을 고려해야 하는 제약이 있었습니다.

1.2.4 테스트가 어려운 구조

최초에 데이터를 조회하는 부분이나 조회한 데이터를 뷰에 맞게 처리하는 부분에서 만큼은 테스트 코드를 추가하는 것을 목표로 설정했습니다. 하지만 현재 구조에서는 다음과 같은 테스트 관련 문제점들이 있었습니다:

  • 데이터 조회와 처리 로직이 다른 로직들과 섞여 있어 단위 테스트가 어려움
  • 웹소켓을 통한 상태 업데이트 로직을 테스트하기 어려운 구조
  • 비즈니스 로직과 UI 로직의 경계가 모호하여 테스트 케이스 작성이 복잡

1.3 개선 방향 설정

1.3.1 커스텀 훅 구현

웹소켓 통신과 상태 관리를 위한 두 개의 독립적인 커스텀 훅을 구현하기로 결정했습니다:

  1. 웹소켓 커스텀 훅
    • 웹소켓 연결 관리
    • 메시지 송수신 처리
    • 메시지 핸들러 인터페이스 제공
  2. 퀴즈존 커스텀 훅
    • 웹소켓 메시지에 따른 상태 업데이트
    • 퀴즈 진행 상태 관리
    • 정답 제출 로직 처리

1.3.2 독립적인 타이머 로직 분리

타이머 관련 로직을 독립적인 커스텀 훅으로 분리하기로 결정했습니다. 이 결정은 MVVM 아키텍처 패턴의 관점에서 볼 때 특히 중요한 의미를 가집니다:

  1. 아키텍처 관점에서의 분리 필요성
    • 타이머는 순수하게 View 레이어의 관심사입니다. 서버와의 동기화가 필요 없는 UI 요소로, ViewModel이나 Model의 개입 없이 View 내에서 독립적으로 관리될 수 있습니다.
    • 반면 퀴즈존의 상태는 서버와의 실시간 동기화가 필요한 비즈니스 로직으로, ViewModel에서 관리되어야 합니다.
  2. 책임 분리의 이점
    • View의 독립성: 타이머는 사용자 인터페이스의 일부로, 서버 상태와 독립적으로 동작할 수 있습니다.
    • 상태 관리 단순화: 퀴즈존 ViewModel은 비즈니스 로직과 서버 상태 관리에만 집중할 수 있습니다.
    • 테스트 용이성: UI 관련 로직과 비즈니스 로직을 명확히 분리함으로써, 각각에 대한 단위 테스트가 용이해집니다.
  3. 개선된 구조
// View Layer - 타이머 커스텀 훅
export const useTimer = ({ initialTime, onComplete }: TimerConfig) => {
  const [time, setTime] = useState(initialTime);
  // ... 타이머 UI 관련 로직
  return { time, isRunning, start, stop };
};

// ViewModel Layer - 퀴즈존 상태 관리
export const useQuizZone = () => {
  const [state, dispatch] = useReducer(quizZoneReducer, initialState);
  // ... 서버 상태 동기화 및 비즈니스 로직
  return { quizZoneState, actions };
};

이러한 구조적 개선을 통해 UI 관련 로직과 비즈니스 로직의 경계를 명확히 하고, 각각의 책임을 적절한 계층에 배치할 수 있게 되었습니다.

2. 리팩토링 방향

2.1 상태 관리 개선

리팩토링의 첫 번째 핵심은 상태 관리 방식의 개선이었습니다. useReducer를 도입하여 분산되어 있던 상태들을 하나의 리듀서로 통합했습니다. 이를 통해 상태 변경의 흐름을 명확하게 추적할 수 있게 되었고, 상태 간의 관계도 더 명확하게 표현할 수 있게 되었습니다. 액션 타입을 명확하게 정의함으로써, 어떤 상황에서 어떤 상태 변화가 일어나는지 쉽게 파악할 수 있게 되었습니다.

// 개선된 상태 관리 구조
type QuizZoneAction =
    | { type: 'init'; payload: QuizZoneLobbyState }
    | { type: 'join'; payload: { players: Player[] } }
    | { type: 'start'; payload: undefined }
    // ...기타 액션들

2.2 비즈니스 로직 중앙화

두 번째로, 분산되어 있던 비즈니스 로직을 리듀서 내부로 통합했습니다. 이제 모든 상태 변경 로직이 하나의 리듀서 함수 내에서 처리되며, 각 액션 타입별로 명확한 상태 전이 규칙을 가지게 되었습니다. 이러한 중앙화된 로직 관리는 코드의 일관성을 높이고, 변경사항 적용을 더욱 용이하게 만들었습니다.

2.3 타이머 로직 분리

마지막으로, 타이머 관련 로직을 완전히 분리했습니다. useTimer를 독립적인 커스텀 훅으로 분리하고, 타이머에서 발생하는 이벤트를 퍼널 상태에 반영하는 방식으로 변경했습니다. 이를 통해 타이머 로직과 퍼널 로직 간의 결합도를 크게 낮출 수 있었고, 각각의 로직을 독립적으로 테스트하고 수정할 수 있게 되었습니다.

3. 리팩토링 결과

3.1 코드 구조 개선

flowchart TB
    subgraph Before["리팩토링 전: 분산된 상태 관리"]
        direction TB
        X1[퀴즈존 상태] --> Y1[changeMainStage]
        X2[풀이 상태] --> Y2[handleQuizCycle]
        X3[진행 상태] --> Y3[proceedToNextQuiz]
        
        Y1 --> Z1[퀴즈존 변경]
        Y1 --> Z2[풀이 상태 초기화]
        Y1 --> Z3[타이머 재설정]
        
        Y2 --> W1[풀이 상태 변경]
        Y2 --> W2[타이머 제어]
        
        Y3 --> V1[다음 문제로 이동]
        Y3 --> V2[상태 초기화]
        
        Z1 --> U1[상태 업데이트]
        Z2 --> U1
        Z3 --> U1
        W1 --> U1
        W2 --> U1
        V1 --> U1
        V2 --> U1
    end
    
    Before --> After

    subgraph After["리팩토링 후: 통합된 상태 관리"]
        direction TB
        A1[Action Dispatch] --> B1[QuizZone Reducer]
        
        B1 --> C1{Action Type}
        
        C1 -->|init| D1[초기 상태 설정]
        C1 -->|start| D2[퀴즈 시작]
        C1 -->|submit| D3[답안 제출]
        C1 -->|nextQuiz| D4[다음 문제]
        C1 -->|finish| D5[종료]
        
        D1 --> E1[새로운 상태]
        D2 --> E1
        D3 --> E1
        D4 --> E1
        D5 --> E1
    end
Loading

리팩토링을 통해 코드의 구조가 크게 개선되었습니다. 단일 책임 원칙을 따르는 훅 구조로 재설계하여, 각 부분이 명확한 역할을 가지게 되었습니다. 특히 외부에 노출되는 인터페이스를 최소화하고 명확하게 정의함으로써, 훅의 사용법을 더욱 직관적으로 만들었습니다.

const useQuizZone = () => {
    const [state, dispatch] = useReducer(quizZoneReducer, initialState);

    // 외부에 노출되는 명확한 인터페이스
    const api = {
        initQuizZoneData,
        submitQuiz,
        startQuiz,
        playQuiz
    };

    return { quizZoneState: state, ...api };
};

이러한 구조는 코드의 가독성을 높일 뿐만 아니라, 다른 개발자들이 훅을 사용할 때 필요한 기능을 쉽게 찾고 이해할 수 있게 해줍니다.

3.2 얻은 이점

상태 관리의 단순화

상태 관리가 하나의 리듀서로 통합되면서, 상태 변화의 흐름을 훨씬 쉽게 추적할 수 있게 되었습니다. 이전에는 여러 개의 useState로 인해 복잡했던 상태 업데이트가, 이제는 명확한 액션과 리듀서를 통해 예측 가능한 방식으로 이루어집니다. 특히 개발 도구를 통한 디버깅이 용이해져서, 문제가 발생했을 때 원인을 파악하기가 훨씬 수월해졌습니다.

비즈니스 로직의 응집도 향상

퀴즈 진행과 관련된 모든 로직이 리듀서 내부로 모이면서, 코드의 응집도가 크게 향상되었습니다. 상태 변경에 관한 규칙들이 한 곳에서 관리되기 때문에, 새로운 기능을 추가하거나 기존 로직을 수정할 때 영향 범위를 쉽게 파악할 수 있게 되었습니다. 또한 이러한 구조는 자연스럽게 코드의 문서화 역할도 수행하여, 다른 개발자들이 비즈니스 로직을 이해하는 데 도움을 줍니다.

타이머 의존성 해결

타이머 로직을 분리함으로써 얻은 가장 큰 이점은 유지보수성의 향상입니다. 이제 타이머 관련 수정사항이 발생하더라도 퍼널 로직에는 영향을 미치지 않으며, 반대로 퍼널 로직을 수정할 때도 타이머 로직을 고려할 필요가 없어졌습니다. 또한 각각의 로직을 독립적으로 테스트할 수 있게 되어, 테스트 커버리지를 높이기도 수월해졌습니다.

4. 향후 개선 방향

4.1 성능 최적화

현재 구조에서 더 나아가, 성능 최적화를 위한 여러 전략을 도입할 계획입니다. 특히 불필요한 리렌더링을 최소화하기 위해 메모이제이션을 적극적으로 활용할 예정입니다. 또한 상태 업데이트가 자주 발생하는 부분들을 배치 처리하여, 렌더링 성능을 개선할 수 있을 것으로 기대됩니다.

4.2 에러 처리 강화

현재의 에러 처리 방식을 더욱 견고하게 개선할 필요가 있습니다. 특히 네트워크 오류나 예상치 못한 상태 변경에 대한 복구 메커니즘을 구현하여, 사용자 경험을 해치지 않으면서도 안정적인 서비스를 제공할 수 있도록 할 계획입니다. 또한 사용자에게 제공되는 에러 메시지를 더욱 명확하고 친절하게 개선할 예정입니다.

4.3 테스트 커버리지 향상

리팩토링을 통해 테스트하기 좋은 구조가 만들어졌으므로, 이를 바탕으로 테스트 커버리지를 높여나갈 계획입니다. 특히 리듀서의 각 액션별 테스트, 타이머 동작 테스트, 그리고 전체적인 퀴즈 진행 흐름에 대한 통합 테스트를 작성 중에 있습니다. 이를 통해 코드의 안정성을 더욱 높일 수 있을 것으로 기대됩니다.

이러한 향후 개선 작업들은 점진적으로 진행하면서, 각 단계마다 충분한 검증을 거쳐 안정적으로 적용해 나갈 예정입니다.

4. 트러블 슈팅

[FE] 실시간 퀴즈 애플리케이션에서의 React 성능 최적화 경험

문제

  • 0.1초마다 타이머 상태 업데이트로 인한 불필요한 리렌더링 발생
  • WebSocket 메시지 핸들러가 컴포넌트 리렌더링 시마다 재생성되는 문제
  • 실시간 상태 업데이트로 인한 퀴즈존 컴포넌트의 잦은 리렌더링

해결 과정

  • useRef를 활용한 빈번한 상태 변경 값 관리
// useTimer
const isRunningRef = useRef(false);
const timerRef = useRef<NodeJS.Timeout | null>(null);
  • WebSocket 인스턴스의 안정적인 참조 관리
// useWebSocket
const wsRef = useRef<WebSocket | null>(null);

학습한 점

  • 실시간 애플리케이션에서 React 훅의 효율적인 활용법 습득
    • useRef: 렌더링과 무관한 값 관리
    • useCallback: 함수의 안정적인 참조 유지
    • useReducer: 복잡한 상태 업데이트 로직 통합
  • 성능 최적화를 위한 상태 관리 전략
    • 빈번한 업데이트가 필요한 값은 ref로 관리
    • 이벤트 핸들러의 안정적인 참조 유지가 중요
    • 복잡한 상태 업데이트는 리듀서로 중앙화
  • 실시간 처리 시 고려사항 이해
    • WebSocket 연결 상태와 같이 자주 변경되는 값은 ref로 관리
    • 메시지 핸들러의 최신 참조 유지가 중요
    • 불필요한 리렌더링 방지가 성능에 큰 영향을 미침

[FE] 이벤트 기반 Reducer 설계를 통한 실시간 퀴즈 상태 관리 개선

문제 : 초기 QuizZoneStage 타입을 직접 Action으로 사용하는 방식은 실시간 이벤트 처리와 상태 변화를 세밀하게 표현하기 어려웠음

type QuizZoneStage = 'LOBBY' | 'IN_PROGRESS' | 'RESULT';
type QuizZoneAction = QuizZoneStage; // stage를 직접 액션으로 사용
  • WebSocket 이벤트와의 매핑을 위해 추가적인 변환 로직이 필요했음
  • 같은 stage 내에서 발생하는 다양한 상태 변화(답안 제출, 참가자 입장 등)를 표현하기 어려웠음

해결 과정

  1. 페어 프로그래밍을 통한 객관적 코드 리뷰

    • 초기 설계자와 객관적 리뷰어로 역할을 나누어 코드의 한계점 파악
    • 실시간 이벤트 처리의 복잡성에 대한 다각적 논의 진행
    • 페어의 서로 다른 관점을 통해 더 나은 해결책 도출
  2. 이벤트 기반 액션 타입 재설계

    type QuizZoneAction =
        | { type: 'init'; payload: QuizZoneLobbyState }
        | { type: 'join'; payload: { players: Player[] } }
        | { type: 'submit'; payload: undefined }
        | { type: 'nextQuiz'; payload: CurrentQuiz };
  3. WebSocket 이벤트와 직접 매핑 구현

    const messageHandler = (event: MessageEvent) => {
        const { event: QuizZoneEvent, data } = JSON.parse(event.data);
        dispatch({
            type: QuizZoneEvent,
            payload: data,
        });
    };
  4. 세부 상태 변화를 포함한 reducer 구현

    case 'nextQuiz':
        return {
            ...state,
            stage: 'IN_PROGRESS',
            currentPlayer: {
                ...state.currentPlayer,
                state: 'WAIT',
            },
            currentQuiz: payload,
        };

학습한 점

  1. 효과적인 상태 관리 설계의 중요성
    • 단순히 현재 상태를 표현하는 것을 넘어, 실제 비즈니스 로직의 흐름을 반영하는 상태 설계가 필요함
    • 페어 프로그래밍을 통해 여러 관점에서 상태 흐름을 검토하여 더 나은 설계 결정으로 이어짐
    • WebSocket 기반 실시간 애플리케이션에서는 이벤트 중심의 상태 관리가 더 적합함을 발견
  2. 실시간 애플리케이션 설계 인사이트
    • WebSocket 이벤트와 상태 관리를 일관되게 처리하는 것의 중요성
    • 이벤트 기반 설계가 실시간 기능 구현에 더 적합함
  3. 리팩토링을 통한 개선
    • 초기 설계의 한계를 인정하고 과감한 재설계를 결정한 것이 좋은 결과로 이어짐
    • 비즈니스 로직 변경에 유연하게 대응할 수 있는 구조를 확보함
    • 새로운 퀴즈 타입이나 게임 모드 추가가 용이한 확장성 있는 구조 달성
  4. 협업의 중요성
    • 페어 프로그래밍을 통해 코드의 문제점을 더 빠르게 발견
    • 서로 다른 관점에서의 피드백이 더 나은 설계로 이어짐
    • 실시간으로 코드 리뷰가 이루어져 빠른 의사결정 가능

[공통] 출제 문제가 노출되는 이슈

  • 문제:
    • 사용자에게 동시에 문제 풀이를 시작하기 위해 미리 다음 퀴즈를 보내줌
    • 기존의 통신에서 문제가 난독화되지 않아 대기시간에 미리 문제를 볼 수 있다.

Screenshot 2024-11-14 at 20.23.07.png

  • 해결 과정:
    • 암호화
      • 문제를 확실히 숨길 수 있음
      • 클라이언트에서도 복호화 키를 가져야함
    • 압축
      • 압축으로 인해 난독화는 물론 통신으로 보내는 데이터의 크기가 줄어듬
      • 압축 알고리즘을 활용해야 하므로 자원이 더 소모됨
    • 인코딩
      • 난독화 가능하면서 가벼움
      • 인코딩 알고리즘을 알고있고, 마음먹으면 쉽게 해석할 수 있음
  • 결과: 인코딩으로 결정!
    • 데이터의 중요성을 봤을때 문제 자체는 보안에 민감한 정보는 아님
    • base64의 난독화 수준이면 클라이언트와 서버의 부담이 없음
    • 문제를 퀴즈존에 저장할 때 인코딩한 문제를 저장하면 퀴즈를 진행할 때 서버에서 부담 없음

[공통] 사용자 정보 수신 REST vs WebSocket

문제 - 현재 퀴즈존에 참여하고있는 모든 사용자의 정보를 어떻게 초기화 할 것인가?

  • API로 퀴즈존에 참가 요청을 하고 응답으로 퀴즈존의 내부 상태들을 받는다.
  • 이후 사용자가 퀴즈존으로 퇴장, 입장 했을 때마다 웹소켓으로 메시지를 보낸다.
  • API 요청과 웹소켓 연결 사이에 접속, 이탈하는 사용자 대한 처리는?

후보

  1. REST + WebSocket 증분 업데이트
  • REST API로 초기 데이터를 받고, 이후 변동사항(접속/이탈)만 WebSocket으로 업데이트
sequenceDiagram
    participant C as Client
    participant S as Server
    participant I as Memory

    C->>+S: REST: 퀴즈존 참가 요청
    S->>I: 현재 사용자 목록 조회
    I-->>S: 사용자 목록 반환
    S-->>-C: 전체 사용자 목록 반환

    C->>S: WebSocket 연결
    Note over C,S: 이후 변동사항만 WebSocket으로 수신
Loading

2. WebSocket 전용 (현재)

  • WebSocket으로 전체 데이터를 받고 이후 변동사항도 WebSocket으로 업데이트
sequenceDiagram
    participant C as Client
    participant S as Server
    participant R as Memory

    C->>+S: REST: 퀴즈존 참가 요청
    S-->>-C: 참가 승인

    C->>S: WebSocket 연결
    S->>R: 전체 사용자 목록 조회
    R-->>S: 사용자 목록 반환
    S-->>C: 전체 사용자 목록 전송
    Note over C,S: 이후 변동사항 WebSocket으로 수신

Loading

3. REST + 시간 기반 동기화

  • REST API로 초기 데이터를 받고, 접속 시간을 이용해 누락된 사용자 정보만 추가로 전송 후 WebSocket으로 변동사항 업데이트
sequenceDiagram
    participant C as Client
    participant S as Server
    participant R as Memory

    C->>+S: REST: 퀴즈존 참가 요청 (timestamp 포함)
    S->>R: 현재 사용자 목록 조회
    R-->>S: 사용자 목록 반환
    S-->>-C: 전체 사용자 목록 + timestamp 반환

    C->>S: WebSocket 연결
    S->>S: timestamp 이후 변경사항 확인
    S-->>C: 누락된 변경사항 전송
    Note over C,S: 이후 변동사항 WebSocket으로 수신
Loading

해결 과정

  • 현재는 WebSocket 전용 방식을 사용하고 있으며, 각 방식은 다음과 같은 특징이 있습니다:
  1. REST + WebSocket 증분 업데이트: 초기 데이터 로딩이 명확하고 네트워크 부하를 분산할 수 있으나, 두 통신 방식 간 일관성 관리가 필요
  2. WebSocket 전용: 구현이 단순하고 통신 채널이 일원화되어 있으나, 초기 연결 실패 시 복구 전략이 필요
  3. REST + 시간 기반 동기화: 데이터 일관성과 누락 데이터 복구가 용이하나, 구현이 복잡하고 시간 동기화에 의존적

프로젝트의 규모와 요구사항을 고려할 때, 어떤 방식이 가장 적합할까요? 특히 300명 이상의 동시 접속자를 고려할 때 각 방식의 장단점에 대해 함께 논의해보면 좋을 것 같습니다.


[공통] 이용 중이던 사용자가 연결이 끊길 경우 재연결 방식

sequenceDiagram
    participant Client
    participant Server
    
    Note over Client: 소켓 연결 끊김 감지
    
    Client->>Server: REST GET 요청
    
    alt 200 OK
        Server-->>Client: 데이터 반환
        Note over Client: 웹소켓 재연결
        Note over Client: 화면 리렌더링
    else 거부
        Server-->>Client: 에러 메시지
        Note over Client: 사용자에게 에러 메시지 노출
    end
Loading

배경상황

  • 퀴즈 진행 중이던 사용자라면, 연결 끊김이나 새로고침, 혹은 재접속 시에 이전의 정보를 복원시키고 진행된 흐름에 맞춰 화면을 재 렌더링 해 줄 필요가 있었다.
  • 반대로 퀴즈 진행 중이지 않던 사용자가 중간에 입장을 시도한 경우, 입장 불가능 하도록 인증 과정이 필요했다.

문제

  • 소켓 연결 끊김을 어떻게 감지할 것이고, 다시 응답을 받아오는 작업을 어떤 식으로 처리해야 하는 지에 대한 고민이 있었다.
  • 인증(해당 퀴즈 존에서 진행 중이던 사용자 확인) 및 인가(퀴즈를 이어서 진행 가능하도록) 과정이 필요했다.

해결 방식

  • 기존 사용자에 대한 인증 ⇒ 쿠키로 진행하도록 했다

  • 연결이 잠깐 끊겼을 경우

    ⇒ 클라이언트에서 연결 끊김을 감지하고 있다가 연결 끊김이 감지되면 서버로 REST GET 요청을 보낸다.

    • 인증 처리 후, 기존의 사용자라면 화면 렌더링에 필요한 정보를 응답에 넣어서 보내준다.
    • 인증 실패 시에는 에러를 응답으로 보낸다.
    • 정상적인 응답이면 다시 WebSocket을 연결한다.

[배포] 4주차 백엔드 + 프론트 통합 과정에 생긴 일들

  • 공유 자원에 동시에 접근하는 문제

    • 해당 문제마다 제출 인원을 세기 위해 제출 시에 count++로 처리하였습니다. count 처리 시 문제가 발생하여 every 고차함수로 플레이어 제출 상태를 확인하여 모두 처리되었을 때 넘기도록 로직 수정
    async checkAllSubmitted(quizZoneId: string) {
            const { players } = await this.quizZoneService.findOne(quizZoneId);
            return [...players.values()].every((player) => player.state === PLAYER_STATE.SUBMIT);
        }
  • 소켓 연결 됐을 때 메세지를 보내도록 메세지큐 처리

    WebSocket Message Queue 도입 설명

    도입 배경 실시간 퀴즈 플랫폼을 개발하면서 로컬 환경에서는 발견하지 못했던 네트워크 불안정성 문제를 테스트 환경과 실제 배포 과정에서 마주하게 되었습니다. 클라우드 환경에서의 네트워크 지연, 일시적인 연결 끊김, 로드 밸런서로 인한 연결 재설정 등의 문제가 빈번하게 발생했고, 이는 메시지 유실로 이어질 수 있는 중대한 위험이었습니다.

    구현 방식 메시지 큐를 배열로 구현하여 WebSocket 연결이 없을 때 메시지를 저장하고, 연결이 수립되면 순차적으로 전송하는 방식을 채택했습니다. useRef를 사용해 리렌더링과 무관하게 메시지를 관리하며, FIFO 방식으로 전송 순서를 보장합니다.

    해결된 문제

    • 네트워크 불안정 상황에서의 메시지 유실 방지
    • 연결 재수립 시 자동으로 메시지 재전송
    • 사용자가 네트워크 상태를 신경 쓰지 않고 서비스 이용 가능

    결론 간단한 메시지 큐 구현만으로도 서비스의 안정성과 사용자 경험을 크게 개선할 수 있었습니다. 향후 메시지 만료 처리, 재시도 로직 등을 추가하여 더욱 견고한 시스템으로 발전시킬 계획입니다.

    const ws = useRef<WebSocket | null>(null);
    const messageQueue = useRef<string[]>([]);
    
    const sendMessage = (message: string) => {
        if (ws.current?.readyState === WebSocket.OPEN) {
            ws.current.send(message);
        } else {
            messageQueue.current.push(message);
        }
    };
    
    ws.current.onopen = () => {
        while (messageQueue.current.length > 0) {
            const message = messageQueue.current.shift()!;
            ws.current?.send(message);
        }
    };
  • nginx 타임아웃 >> 기본 설정 60초, 300초로 변경

    proxy_read_timeout 300s;  # read 비활성 연결 유지 시간 연장
    proxy_send_timeout 300s;  # send 비활성 연결 유지 시간 연장
    • 퀴즈 풀이 도중 연결이 끊기는 상황을 조우했고, 해결책을 찾는 과정에서 서버와 60초(기본값) 동안 통신하지 않으면 연결이 자동으로 끊긴다는 것을 알게 되었습니다.
    • 그래서 저희는 위와 같은 문제를 해결하기 위해 비활성 연결 유지시간을 엔진엑스 설정 상에서 명시적으로 늘려주었습니다.

5. 배포 및 시연

이번주는 다수가 한방에 접속해서 퀴즈를 진행할 수 있도록 4주차 목표를 반영해서 배포 까지 완료 했습니다. 진행하면서 발생할 에러가 다수 있을 거라 참가하실 때 혹시 에러가 있다면 피드백에 달아주시면 감사하겠습니다!

6. 다음주 목표

사용성을 위한 CRUD 작업 보다는 더 많은 트래픽을 처리하기 위한 개선과 확장성을 위한 작업 진행

주차별 목표 달성 현황

프로젝트를 시작하면서 주차별로 현실적이고 달성 가능한 목표를 설정했습니다. 1주차는 기획과 에픽-스토리 도출에 집중했고, 2주차에는 단일 퀴즈존에서의 웹소켓 기반 실시간 퀴즈 풀이를 구현했습니다. 3주차에서는 다중 사용자 지원과 세션 관리를 완성했으며, 4주차에는 복수의 퀴즈존 생성 및 비동기 운영이라는 목표를 달성했습니다.

이러한 체계적인 접근 덕분에 4주차까지의 목표를 성공적으로 달성할 수 있었고, 현재는 기본적인 서비스의 틀을 갖춘 상태입니다.

5주차 목표

1. 성능 및 부하 테스트

목표로 하는 300명 이상의 동시 접속자를 안정적으로 처리할 수 있는지 검증하려 합니다. 첫 메시지와 마지막 메시지의 수신 시간 차이를 측정하여 실시간성을 검증하고, Artillery나 K6 같은 성능 테스트 도구를 활용해 다양한 시나리오에서의 서버 부하 상태를 확인할 예정입니다.

2. 백엔드 코드 리팩토링

코드 전반에 걸쳐 사용하지 않는 로직들을 정리하고, 클린 코드 관점에서 개선이 필요한 부분들을 수정할 예정입니다. 특히 중복된 코드들을 모듈화하고, 책임과 역할이 명확하도록 구조를 개선하려 합니다. 이후 웹소켓 부분에서도 기본 WebSocket Adapter를 커스텀하고 Guard를 추가하여 더 안정적인 시스템을 구축할 계획입니다.

3. 확장성 개선

현재 단일 서버로 운영되고 있는 시스템을 Scale-out이 가능한 구조로 개선하려 합니다. Redis를 도입하여 웹소켓 연결 정보를 관리하고, 여러 서버 간에 실시간 이벤트를 공유할 수 있는 구조로 만들 예정입니다.

추가 구현 사항

시간이 허락한다면 기존 웹소켓에 채팅 이벤트를 추가하여 실시간 채팅 기능도 구현할 예정입니다. 또한 놓쳤던 코드 리뷰를 진행하고, 퀴즈존 CRUD 작업도 진행할 계획인데, 이는 사용성보다는 트래픽 처리와 확장성 개선에 초점을 맞추어 진행하려 합니다.

향후 방향

지금까지 계획했던 기능들의 구현은 어느 정도 마무리된 상태입니다. 이제는 실제 서비스 환경을 고려하여 서비스의 품질과 안정성을 한 단계 더 끌어올리는 데 집중하려 합니다.

7. 질의응답

5주차

원본 링크: https://www.figma.com/design/1CdBFnF3oWXgAzRdgEhRNU/Web08?node-id=82-305&t=TpeWEEVNNIxrTWHU-1

발표자료

프로젝트 및 팀원 소개

Web08

Team - 하하

Project - BooQuiz

https://github.com/boostcampwm-2024/web08-BooQuiz

스크린샷 2024-11-28 오후 1.59.36.png

핵심 그라운드 룰

프로젝트 - BooQuiz

프로젝트 개요

저희 프로젝트는 실시간으로 300명의 부스트캠퍼들이 동시에 참여할 수 있는 퀴즈 플랫폼

핵심 기능

  • 실시간 퀴즈 진행
  • 실시간 채팅 시스템
  • 실시간 자동 채점

기술 스택

  • 프론트엔드: React, Vite, Tailwind, Shadcn UI
  • 백엔드: Nest.js typeOrm
  • 공통: webSocket, typescript
  • 인프라 : ncp, nginx, mysql

프로젝트 자세한 소개(시연)

잠시 퀴즈 풀어보고 오겠습니다...

https://booquiz.kro.kr/hello

피드백 링크

https://docs.google.com/presentation/d/1yZ2OjVjJpCTsLSLaUyujPG48VzlffzaNyIDGlK1rKIM/edit#slide=id.g31af0ba930e_0_0

  • 에러 제보 환영!
  • 사용자 피드백 환영!

프로젝트 목표 사항 및 성과

1. 지속적인 배포

  • 매주 정기적인 배포 진행
  • 데모 시연을 통한 진행 상황 공유

2. 현실적인 목표 설정

  • 달성 가능한 범위 내 기능 구현
  • 코어타임을 준수하며, 개발 시간과 개인 학습 그리고 개발에 대한 고민을 할 수 있는 시간 확보

프로젝트 성과

체계적인 개발 관리

  • 주간 단위 리팩토링 시간 확보
  • 적절한 목표 설정으로 개인 학습과 개발 시간의 균형 달성

문서화 강화

  • 학습 내용 문서화
  • 개발 과정 및 결과물 문서화

개발 타임라인

  • 커밋 기록과 문서화 해둔 내용을 바탕으로 완성된 저희 프로젝트의 현재까지 타임라인입니다.
  • 5주간의 경험을 정리하는 것이 좋을 것 같다는 notice의 공지를 보고 정리해보았습니다.
timeline
    title BooQuiz 프로젝트 개발 타임라인
    
    section 0주차
      프로젝트 기획 단계: 🎯 팀 빌딩 및 아이디어 도출 : 🔄 서비스 기능 구체화 : 🛠️ 기술 스택 및 개발 프로세스 확정

    section 1주차 
      프로젝트 기반 구축: 🏗️ 모노레포 환경 구성 : 📝 기술 문서화 : 🔧 개발 컨벤션 정립

    section 2주차
      기반 기능 구현: 🎨 UI/컴포넌트 라이브러리 구축 : 💻 퀴즈존 모듈 설계 : 🔌 WebSocket 기본 연결 구현

    section 3주차
      단일 사용자 기능: 🏗️ 퀴즈 진행 상태관리 구현(FE) : ⚡ 퀴즈 진행/채점 로직 : 🚀 CI/CD 및 Docker 구성

    section 4주차
      다중 사용자 지원: 📦 퀴즈 진행 상태관리 리팩토링 : 🔌 WS 메시지 큐 구현 : 👥 동시접속 및 권한 관리 : 📦 백엔드 통합 리팩토링

    section 5주차
      시스템 고도화: ⚡ WebSocket 최적화 : 💾 DB 연동 : 📊 실시간 순위 시스템 : 🎨 UI/UX 개선
Loading

0주차 → 팀 빌딩 + 아이디어 도출

timeline
   title 0주차 (10/22 ~ 10/27)
   
   section 0주차 - 팀 빌딩 (10/22 ~ 10/27)
     10/22(일) : 팀원 자기소개 및 역량 공유
               : 프로젝트 아이디어 브레인스토밍
               : BooQuiz(도전골든벨) 초기 아이디어 도출
     
   section 아이디어 구체화 (10/24)
     10/24(화) : BooQuiz 서비스 세부 기능 논의
               : 실시간 참여형 퀴즈 플랫폼 컨셉 확정
               : 트래픽 테스트를 위한 방 인원 조절 기능 구상
               : 부스트캠프 테마 퀴즈 콘텐츠 기획
   
   section 기술 스택 및 규칙 확정 (10/27)
     10/27(금) : 🔄 기술 스택 최종 확정
               : FE - React / BE - NestJS
               : 🔧 개발 프로세스 설정
               : Git 브랜치 전략 수립 (main-dev-feat)
               : PR 리뷰 프로세스 확립, 테스트 코드 작성 원칙
               : 🔄 프로젝트 그라운드 룰 설정
Loading
  • 팀 빌딩 및 아이디어 도출 진행

    image.png

  • 도출된 주제에 맞춰 메인 기술 스택 선정

  • 개발 프로세스와 협업 규칙 수립

1주차 - 초기환경 구축

timeline
   title 1주차 (10/28 - 11/02)
   
   section 1주차 - 프로젝트 기반 구축
     10/28(월) : 🔄 공통/기반 : 프로젝트 초기화, 모노레포 설정
     10/30(수) : 👥 저녁 팀멘토링
     10/31(목) : 🔄 공통/기반 : README 작성, 기술 스택 선정
     11/01(금) : 📊 1주차 데모데이
     11/02(토) : 🔧 DevOps/기반 : 이슈/PR 템플릿 추가, 개발 컨벤션 정립
     11/02(토) : 👤 오후 동현님 개인 멘토링
Loading
  • 모노레포 설정, 디렉토리 구조 세팅

    graph TD
        Root --> apps
        Root --> docs
        Root --> node_modules
        Root --> packages
    
        apps --> fe[frontend]
        apps --> be[backend]
    
        Root --> configs[주요 설정 파일]
        configs --> workspace[pnpm-workspace.yaml]
        configs --> pkg[package.json]
    
        style Root fill:#f9f,stroke:#333,stroke-width:2px
        style apps fill:#faa,stroke:#333,stroke-width:2px
        style fe fill:#aff,stroke:#333,stroke-width:2px
        style be fill:#aff,stroke:#333,stroke-width:2px
    
    Loading
  • 패키지 관리자 선정(pnpm)

  • 프론트 코드 번들링 방식 설정 (Vite)

  • 깃 훅 세팅 (husky)

2주차 - 프로젝트 기반 작업

timeline
   title 2주차 (11/03 - 11/09)
   
   section 2주차 - 기반 기능 구현
     11/03(일) : 🎨 Frontend/기반 : shadcn UI & Tailwind 설정
               : 🔧 DevOps/기반 : Husky & Commitlint 설정, Prettier 설정
               : 💻 Backend/기반 : 퀴즈존 모듈/리포지토리 인터페이스 설계
     11/04(월) : 🎨 Frontend/기반 
               : Button, Typography, CommonInput 컴포넌트
               : ContentBox, Layout 컴포넌트 구현
               : 💻 Backend/기반 
               : 퀴즈존 메모리 저장소 구현
               : 세션/쿠키 설정
		           : 👥 저녁 현우님 개인 멘토링
     11/05(화) : 🎨 Frontend/기반 
               : Storybook 설정 및 컴포넌트 스토리 작성
               : 💻 Backend/기반 
               : 컨트롤러/서비스 레이어 테스트 코드
               : 👥 저녁 준현, 현민, 선빈 개인 멘토링
     11/06(수) - 11/07(목) : 💻 Backend/기능 
               : WebSocket 게이트웨이 구현
               : 기본 연결 및 이벤트 핸들링 구현
               : SuperWSTest 기반 WS 테스트
     11/07(목) : 🎨 Frontend/기능: Alert Dialog, TextCopy 컴포넌트 추가
               : 🔄 2주차 통합
               : 공통 컴포넌트 라이브러리 완성
               : WebSocket 기본 연결 테스트
     11/08(금) : 📊 2주차 데모데이
     11/09(토) : 🧘 2주차 모각글
Loading
  • 프론트 : UI 기반 작업 (페어 프로그래밍)

    • shadcn ui, tailwind 설정, 공통 컴포넌트 개발

      CommonButton_Storybook.gif

  • 백엔드 : Nest 기본 구조 설계, webSocket 게이트웨이 구현 및 연결 테스트 작업(페어 프로그래밍)

3주차 →단일 사용자 퀴즈 풀이 기능

timeline
    title 3주차 (11/12 - 11/18)
    
    section 퀴즈존 기본 구현
      11/12(일) - 11/13(월) : 🎨 Frontend/기능
                : QuizZoneManager 초기 상태관리 구현
                : 퀴즈존 생성 페이지 퍼널 구조 구현
                : 💻 Backend/기능
                : 실시간 퀴즈 진행 이벤트 처리

    section UI/UX 개선
      11/13(월) - 11/14(화) : 🎨 Frontend/기능
                : Timer 컴포넌트 개선
                : Progress Bar 애니메이션 구현
                : 💻 Backend/기능
                : 답안 제출 및 채점 로직
                : 🔧 DevOps/기반
                : Winston 로거 구현
                : GitHub Actions CI/CD 파이프라인 구축

    section 실시간 기능 개선
      11/14(화) - 11/15(수) : 🎨 Frontend/기능
                : 실시간 점수/랭킹 표시
                : 퀴즈 결과 화면 개선
                : 💻 Backend/기능
                : API 문서화 (Swagger/TSDoc)
                : 🔧 DevOps/기반
                : staging 환경 구축
                : 👥 저녁 팀멘토링
      11/15(목) : 💻 Backend/기반
                : Docker 컨테이너 설정
                : 환경변수 분리
                : 🔄 3주차 통합
                : 실시간 퀴즈 진행 테스트
                : 단일 사용자 퀴즈 풀이 흐름 검증
       11/24(금) : 📊 5주차 데모데이
	     11/25(토) : 🧘 5주차 모각글
Loading
  • 프론트는 퀴즈 진행과 관련된 핵심 UI 컴포넌트 개발

  • 백엔드는 퀴즈 진행과 관련된 소켓 이벤트 처리, 입장 및 결과와 관련된 기능 구현에 집중.

    sequenceDiagram
        participant Client
        participant Server
        participant Memory
    
        Note over Client,Memory: 1. Quiz Zone Setup
        Client->>Server: Create Quiz Zone
        Server->>Memory: Store Quiz Session (In-Memory)
        Server-->>Client: Return Quiz Zone ID
    
        Note over Client,Memory: 2. Game Flow
        Client->>Server: WebSocket Connection
        Server-->>Client: Connection Established
        
        Client->>Server: Start Quiz
        Server->>Memory: Update Game State
        Server-->>Client: Send First Quiz
    
        Note over Client,Memory: 3. Quiz Cycle
        loop For Each Quiz
            alt Answer Submitted
                Client->>Server: Submit Answer
                Server->>Memory: Store Answer
            else Time Out
                Server->>Client: Quiz Timeout
            end
            
            Server->>Client: Next Quiz / Finish
        end
    
        Note over Client,Memory: 4. Game End
        Server->>Client: Send Summary(Final Results)
    
    Loading
  • CICD(깃헙 액션), Docker 세팅, nginx 프록시 서버 세팅 등 관련 인프라 작업도 병행

    DevOps 아키텍처

    DevOps 아키텍처

4주차 → 다수 사용자 플레이로 기능 확장

timeline
   title 4주차 (11/18 - 11/23)
   
   section 4주차 - 다수 사용자 동시 참여 구현
     11/17(일) - 11/20(월) : 🎨 Frontend/개선
               : useTimer, useQuizZone 훅 분리
               : useWebSocket 커스텀 훅 구현
               : 💻 Backend/기능
               : 10인 동시접속 처리 로직
               : 참여자 상태 관리 개선
     11/20(월) - 11/21(화) : 🎨 Frontend/개선
               : 퀴즈존 타입 시스템 정의
               : 상태 머신 기반 이벤트 처리
               : 💻 Backend/개선
               : 퀴즈존 리포지토리 리팩토링
               : 방장 기반 권한 시스템
               : 🔄 참여자 관리 통합
               : 방장-참여자 권한 테스트
               : 👤 저녁 동현님 개인 멘토링
     11/21(화) : 🎨 Frontend/테스트
               : useTimer 테스트 코드 작성
               : useQuizZone 테스트 코드 작성
               : 💻 Backend/개선
               : 퀴즈 정답 제출 로깅 추가
     11/21(화) - 11/22(수) : 🎨 Frontend/개선
               : 커넥션 비정상 종료 처리
               : 💻 Backend/개선
               : 다중 사용자 연결 관리 개선
               : 👥 저녁 현민, 선빈님 개인 멘토링
     11/22(수) - 11/23(목) : 🎨 Frontend/개선
               : WebSocket 메시지 큐 시스템
               : base64 인코딩/디코딩 처리
               : 💻 Backend/개선
               : 동시성 처리 검증
               : 🔧 DevOps/개선
               : Docker 배포 환경 구축
               : 🔄 4주차 통합
               : 다중 사용자 동시 퀴즈 진행 테스트
               : 👥 저녁 현우, 준현 개인 멘토링
     11/24(금) : 📊 4주차 데모데이
     11/25(토) : 🧘 4주차 모각글
Loading
  • 프론트엔드 : 커스텀 훅 분리 및 개선(useTimer, useQuizZone, useWebSocket)
  • 백엔드 : 방 별 웹소켓을 논리적으로 분리, 방장 권한 시스템 구현, 인스턴스 하나에 test용 샌드박스 환경 구축하여 테스트 서버로 설정
  • 공통 : 소켓 연결 끊김 상황, 중간 새로고침 등 예외 상황에 대한 처리

5주차 → 기능 고도화 및 안정화

timeline
   title 5주차 (11/24 ~ 11/27)
    
    section 5주차 - 기능 고도화 및 DB 연동
     11/24(일) - 11/25(월) : 💻 Backend/개선
               : WebSocket 커스터마이징
               : play gateway 리팩토링
               : 엔티티 분리 및 연결 로직 개선
     11/25(월) : 💻 Backend/개선
               : play 모듈 제출/진행 처리 개선
               : timeout, summary 로직 리팩토링
               : 사용자 연결 관리 개선
     11/26(화) : 🎨 Frontend/기능
               : 퀴즈 풀이 실시간 정보 표시 추가
               : 결과 페이지 UI/UX 개선
               : 비동기 에러 처리 추가
               : 💻 Backend/기능
               : 닉네임 변경 기능 추가
               : 점수 기반 순위 시스템 구현
               : 👥 저녁 팀멘토링
     11/27(수) : 🎨 Frontend/개선
               : UI 피드백 반영 및 개선
               : 💻 Backend/기능
               : SQLite 로컬 DB 연동
               : 퀴즈 검색 기능 추가
               : 🔄 5주차 통합
               : SQLite 기반 퀴즈 관리 기능 검증
               : 실시간 순위 및 피드백 시스템 테스트
     11/27(금) : 📊 5주차 데모데이
     11/28(토) : 🧘 5주차 모각글
Loading

마스터 클래스 및 데모발표에서 받은 피드백 반영하여 기능 및 UI수정, 그리고 기능 고도화 및 안정화에 집중

  • 백엔드 : webSocket 로직 개선, playGateway 리팩토링, 부하 테스트
  • 프론트엔드 : 추가된 기능에 맞춰 ui를 추가하고, 실시간성이 잘 드러나게 ux를 개선
  • 공통 : 실시간성을 더 잘 드러낼 수 있도록 제출한 사람, 실시간 채팅, 맞은 사람 비율 등의 기능 추가

5주차 까지 현재 프로젝트 구조

stateDiagram-v2
    Frontend --> WebSocket_Gateway: WebSocket 연결
    Frontend --> REST_API: HTTP 요청
    WebSocket_Gateway --> Quiz_Manager: 실시간 퀴즈 관리
    REST_API --> Quiz_Service: 퀴즈 CRUD
    Quiz_Manager --> Server: 세션 & 상태 관리
    Quiz_Service --> Server: 영구 데이터 저장

    state Server {
        MySQL --> 퀴즈/사용자데이터
        인메모리 --> 실시간세션/순위
    }

Loading

이번주 세부 작업

1) 채팅 구현

실시간성을 강화하기 위해 채팅 기능 구현

스크린샷 2024-11-28 오후 10.11.41.png

  • 기존의 웹소켓을 활용하되, chat이라는 새로운 이벤트를 추가하여 서버에서 채팅 이벤트 처리가 가능하도록 구현.
  • 프론트에서는 chatBox 컴포넌트 구현 ⇒ 채팅 박스를 필요한 곳에서 계속 재사용 가능하도록 구현

2) DB 연결 및 퀴즈셋 만들기

지금까지는 퀴즈셋이 고정이고, 로그인도 필요 없었기 때문에 데이터베이스에 대한 필요성도 없었으나, 퀴즈셋을 커스텀할 수 있는 기능 추가를 위해 mysql 설정 연결.

  • 퀴즈셋 커스텀은 ⇒ 사용자가 방을 생성 할 때, 혹은 퀴즈셋을 미리 만들어 두고 싶을 때 직접 퀴즈셋을 만들고 데이터베이스에 저장 시킬 수 있는 기능

3) UI 피드백 반영, 채팅 컴포넌트 추가 및 모바일 뷰 적용

지난주 데모 데이에서 받았던 피드백과 마클 시간에 크롱님께 받은 피드백을 바탕으로 UI 전면 수정을 진행 + 채팅 컴포넌트 구현으로 인한 전체 UI 수정

  • 지난주에 진행 된 데모데이마스터클래스 에서 받은 피드백을 바탕으로 전반적인 UI에 대해서 수정을 진행 하였습니다. 사용자 경험을 고려하여 UI의 사이즈에 대해서 수정을 했고, 툴팁을 포함하여 사용자에게 추가적인 정보를 제공하도록 추가하였습니다.

스크린샷 2024-11-28 오후 8.37.48.png

스크린샷 2024-11-28 오후 8.38.10.png

스크린샷 2024-11-28 오후 8.26.11.png

  • 위의 작업 이후에 채팅 기능이 추가 되게 되면서 전반적인 UI에 대해서 수정을 진행하였습니다.
  • 미디어 쿼리를 이용해서 기존의 UI 사이즈를 조정하고 모바일에서도 정상적으로 사용할 수 있도록 수정했습니다.

스크린샷 2024-11-28 오후 8.39.10.png

스크린샷 2024-11-28 오후 8.39.47.png

4) 부하테스트

Artillery 툴로 yml 파일을 실행시켜 부하테스트를 진행

  • 퀴즈 입장과 제출에 대한 부하테스트
config:
  target: "wss://booquiz.kro.kr/api/play"
  plugins:
    metrics-by-endpoint: {}
  phases:
    # 1단계: 200
    - duration: 30
      arrivalCount: 200
    # 2단계: 400
    - duration: 30
      arrivalCount: 200
    # 3단계: 600
    - duration: 30
      arrivalCount: 200
    # 4단계: 800
    - duration: 30
      arrivalCount: 200
    # 5단계: 부하 상태에서 안정성 확인
    - duration: 60
      arrivalCount: 0

scenarios:
  - name: "퀴즈존 실시간 게임 테스트"
    engine: "ws"
    flow:
      - send:
          type: "join"
          data:
            quizZoneId: "wsloadtest"
          wait: 1000

      # 답안 제출
      - think: 5
      # 여러 문제 풀이
      - loop:
          - send:
              type: "submit"
              data:
                index: "{{ $loopIndex }}"
                answer: "포도당"
                submittedAt: "{{ $timestamp }}"
              wait: 1000
          - think: 3
        count: 3  # 3문제 풀이 시뮬레이션

      - think: 5

테스트 결과 (클로드 정리본)

  • 총 테스트 시간: 2분 21초
  • 총 생성된 사용자: 800명
  • 성공한 세션: 800명 (성공률 100%)
  • 실패한 세션: 0명
  • 총 전송된 메시지: 3,200개
  • 평균 메시지 전송률: 22/초
  1. 단계별 분석
    • 1단계 (0~30초): 200명
      • 초당 약 6-7명씩 접속
      • 메시지 전송률: 27/초까지 안정적 증가
    • 2단계 (30~60초): 400명
      • 추가 200명 접속
      • 메시지 전송률: 27/초로 안정적 유지
      • 세션 시간 일관성 유지 (약 19초)
    • 3단계 (60~90초): 600명
      • 추가 200명 접속
      • 메시지 전송률: 27/초 유지
      • 오류 없이 안정적 처리
    • 4단계 (90~120초): 800명
      • 마지막 200명 접속
      • 메시지 전송률 약간 감소 (24/초)
      • 여전히 오류 없이 처리
  2. 성능 지표
    • 세션 지속 시간:
      • 최소: 19.03초
      • 최대: 19.52초
      • 평균: 19.05초
      • 변동폭: 0.49초 (매우 안정적)
  3. 주목할 만한 점
    • 모든 단계에서 오류 발생 없음
    • 세션 시간이 매우 일관적
    • 800명까지 안정적으로 처리
    • WebSocket 연결 유지 성공
    • 메시지 처리 성능 유지
  4. 결론
    • 현재 서버는 800명의 동시 접속을 안정적으로 처리 가능
    • 실제 퀴즈 게임 시나리오에서도 성능 유지
    • 추가 부하 테스트 가능할 것으로 보임
💡

결과 요약 by 클로드
이 테스트 결과는 매우 긍정적이며, 서버가 예상보다 훨씬 더 많은 동시접속자를 안정적으로 처리할 수 있음을 보여줍니다. 1,000명 이상의 테스트도 고려해볼 만합니다.

마지막) NCP-monitoring 앱과 슬랙 연결 작업

  • Naver Cloud Insight 활용 하여 알림 조건을 항목 별로 설정
  • 모니터링 앱을 슬랙에 연동하여 조건에 해당하면 슬랙에 알림이 오도록 설정

스크린샷 2024-11-28 오전 3.50.37.png

image.png


다음주 목표

마지막 주차인 만큼 기능 구현보다 프로젝트 완성도를 높이는 데 시간을 쓸 생각입니다.

  • [FE, BE] 방장 페이지 구현(방장 or 일반 사용자에 따른 별도의 페이지 및 별도의 프로세스)

  • [FE] 컴포넌트 별 테스트 코드, 스토리북 스토리 작성

  • [FE] 프론트엔드 컴포넌트 리팩토링

    • 뷰와 로직을 분리해서 관리할 수 있도록 하기
  • [BE] 전반적인 코드 리팩토링

  • [BE] 퀴즈 시간 동기화 개선

  • [Docs] 리팩토링과 향후 방향성을 고려한 인수인계 문서 만들기

  • [Docs] 노션 프로젝트 문서 ↔ 깃허브 리드미, 위키 동기화

  • [공통] 멘토님이 던져주신 질문 고민해보기

    우리 프로젝트에서 더 고민해볼 수 있는 부분을 찾고 생각해보자

    • [FE] URL 기반 상태 관리 방식 검토
    • [BE] Redis 도입을 통한 퀴즈 관련 정보 관리 방안
    • [BE] 모놀리스에서 MSA로의 전환 가능성 검토
      • 채팅 서비스와 REST API 서비스 분리 방안
    • [BE] 채팅 데이터 저장소 선정 및 관리 방안

질의응답 타임!


https://docs.google.com/presentation/d/1yZ2OjVjJpCTsLSLaUyujPG48VzlffzaNyIDGlK1rKIM/edit#slide=id.g31af0ba930e_0_0

⚠️ **GitHub.com Fallback** ⚠️