Skip to content

Commit 3caf77e

Browse files
committed
2 parents e9d18ba + f5f73cd commit 3caf77e

File tree

7 files changed

+159
-94
lines changed

7 files changed

+159
-94
lines changed

README.md

Lines changed: 105 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,155 @@
1-
# RealTicket
1+
![Group 1](https://github.com/user-attachments/assets/0a330f32-ca85-4ff7-895f-48b242dfcdd8)
22

3-
[📚팀 노션](https://chestnut-sense-efd.notion.site/RealTicket-12d313ed69ba805cb271cd1f51f8272b?pvs=4)
4-
[🎟️RealTicket 홈페이지](http://www.realticket.store/program)
3+
<a href="http://www.realticket.store/">
4+
<img src="https://github.com/user-attachments/assets/9843f504-987b-468c-b951-c2509ce8a958" width="525" alt="Real-Ticket 이용하기(배포 링크)">
5+
</a>
56

6-
## 팀 1호선: 팀원 소개
7+
# 🚀 실시간 좌석 예약 서비스
78

8-
> 모두 1호선에 살고 있어요
9+
<aside>
910

10-
<table>
11-
<tbody>
12-
<tr>
13-
<td align="center"><a href="https://github.com/KWAKMANBO"><img src="https://github.com/user-attachments/assets/c6555879-8355-4ba3-b8c0-1ef4ad2b5d22" width="100px;" alt=""/><br /><sub><b>J05_곽희상</b></sub></a><br />1호선 당정역</td>
14-
<td align="center"><a href="https://github.com/Donggle0315"><img src="https://github.com/user-attachments/assets/f273520b-a7a9-4bae-85aa-819785192065" width="100px;" alt=""/><br /><sub><b>J033_김동현</b></sub></a><br />1호선 동암역</td>
15-
<td align="center"><a href="https://github.com/qkdl60"><img src="https://github.com/user-attachments/assets/2aefb797-9942-482b-86e5-c776b649aca6" width="100px;" alt=""/><br /><sub><b>J102_박노철</b></sub></a><br />1호선 대방역</td>
16-
<td align="center"><a href="https://github.com/kyu4583"><img src="https://github.com/user-attachments/assets/f0d1aad8-fe7c-4233-b42c-30ee7b946283" width="100px;" alt=""/><br /><sub><b>J144_신성규</b></sub></a><br />1호선 주안역</td>
17-
</tr>
18-
</tbody>
19-
</table>
11+
예약 가능하다고 믿었는데… 이미 예약 완료? 😟
12+
13+
이제 실시간으로 바뀌는 좌석 상태를 한눈에 확인하세요! 🚀
14+
15+
</aside>
16+
17+
# 🎫 주요 기능
2018

21-
### 우리팀의 강점
19+
- 자리 선택과 동시에 남에게 뺏길 걱정 NO!
20+
- 실시간으로 바뀌는 예매 현황 조회
21+
![실시간-예매-숏-샘플](https://github.com/user-attachments/assets/0bea6008-1770-4098-a4ce-a25364c36ec8)
2222

23-
1. 꾸준한 회의록 아카이빙!! - [🧑‍🤝‍🧑회의록](https://www.notion.so/12d313ed69ba808e8b37e8249f515f5f?pvs=4)
24-
2. 빠른 피드백과 소통
25-
3. 오프라인 만남을 통한 전우애
23+
---
2624

25+
## 🎯 **개발 목표**
2726

27+
<aside>
28+
🎟️
2829

30+
**끊김 없는 서비스 제공**: 많은 사용자가 동시에 접근해도 안정적으로 실시간 서비스 제공.
2931

32+
</aside>
3033

31-
# 프로젝트 개요
34+
---
3235

33-
> 🎟️ “RealTicket”
36+
## **🛠️ 문제 해결 과정**
3437

35-
https://github.com/user-attachments/assets/1262b03f-9149-4da0-84c0-55853dd5bf7e
38+
### Server Sent Event를 통한 서버 리소스 절약
3639

37-
"실시간으로 좌석을, 실수 없이 예매하다!”
40+
- 기존에 선택한 WebSocket을 통한 실시간 통신은 서버가 클라이언트의 요청을 계속해서 수신해야 하기에 서버의 리소스 부담으로 이어진다.
41+
- 서버가 클라이언트에게 일방적으로 데이터를 푸시하는 Server Sent Event로 실시간 통신 방식을 변경한다.
42+
- 서버가 더 이상 클라이언트 요청 수신에 대한 리소스를 낭비하지 않게 된다.
3843

39-
구닥다리 인터파크 티켓은 이제 안녕.
44+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/WebSocket-VS-SSE-d8fed9e7c2bc46318b565dc775b6535a?pvs=4)
4045

41-
현대에 걸맞는 UX를 추구하는 새로운 티켓팅 서비스
46+
---
47+
### 데이터 발행 비용 절약을 위한 SSE 브로드캐스팅
4248

43-
### 서비스 개요
49+
- 기존 방식은 클라이언트마다 각각 데이터가 발행돼, 구독자가 늘어남에 따라 오버헤드가 커지는 문제가 있었다.
50+
- 동일한 데이터 소스를 여러 클라이언트가 참조하는 형태의 브로드캐스팅을 구현했다.
51+
- 구독자의 수에 상관 없이 데이터 발행에 드는 비용을 고정적으로 줄일 수 있었다.
4452

45-
<img src ="https://github.com/user-attachments/assets/bb849a55-bf8c-457d-9668-5d5105acbfe8" width="400" height="200">
53+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/SSE-60d6e13d559a4376a7959534b26f7516?pvs=4)
4654

55+
---
56+
### 대기열 도입을 통한 트래픽 과부하 방지
4757

48-
#### 😢 기존 서비스의 불편함
49-
기존 인터파크, YES24와 같은 플랫폼을 통해 예매시 좌석을 선택하고 예매를 진행하면 위처럼 이미 선택된 좌석이라는 문구가 나오는 경우가 많았습니다. 일반적으론 다시 이전화면으로 돌아가 예매를 진행해야되는데 이미 예매가 끝나있어 자리를 잡지 못하고 하염없이 취소표를 기다리거나 아니면 아예 포기해야하는 불편함이 있었습니다.
58+
- 다수의 사용자를 좌석 선택 화면에 머물게 하면 서버에 부하가 점차 가중된다.
59+
- 서버에 몰리는 사용자 수에 제한을 걸어 트래픽을 조절하기 위해 대기열 시스템을 도입한다.
60+
- 대기열 도입을 통해서 좌석 선택 페이지에 몰리는 SSE 연결을 조절하여 서버의 안정성을 확보한다.
5061

51-
#### ☺️ 우리 서비스는?
52-
RealTicket 이러한 불편함을 해소하기위해 정말 실시간으로 선점되는 좌석을 확인하면서 예매할 수있는 실시간 예매 서비스를 제공하고자합니다.
62+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/8800343f85c644eca5dd1e373e4ef4b2?pvs=4)
5363

64+
---
65+
### 상태 관리와 추적을 통한 대기열 누수 방지
5466

67+
- 대기열 화면과 좌석 선택 화면 사이에서 클라이언트의 갖은 예외적 움직임이 발생했고, 대기열 운영에 오류를 일으켰다.
68+
- 변수를 통제하는 방향으로 유저의 상태를 세분하게 정의, 추적, 관리했다.
69+
- 예외적 상황에도 누수 없이 정확한 인원 추적으로 서비스의 무결성을 유지할 수 있게 되었다.
5570

56-
## 기능 흐름
71+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/f8278c76f2144c18bc4776a69e1563a7?pvs=4)
5772

58-
### 컨텐츠 선택
73+
---
74+
### 매크로 방지 문자를 통해 부하 분산
5975

60-
![image](https://github.com/user-attachments/assets/479ba59f-b009-4fb4-adeb-45356010f945)
76+
- 예매 오픈 시 다수의 클라이언트가 동시에 좌석 점유 페이지로 이동해 서버에 과도한 부하가 발생한다.
77+
- 매크로 방지 기능을 사용해 사용자 입력을 요구하여 의도적으로 지연을 발생시킨다.
78+
- 동시에 들어오는 요청을 분산시켜 서버의 안정성을 확보한다.
6179

62-
- 예매하고 싶은 컨텐츠를 선택합니다.
80+
[[자세히 보기]](https://www.notion.so/0a66d01543eb49749e58eb9911c411f1?pvs=4)
6381

64-
### 예매 입장 / 대기 큐
82+
---
83+
### 좌표 기반 데이터로 통신 비용 절약
6584

66-
![image](https://github.com/user-attachments/assets/1288e4ba-baf4-48a4-a7ed-e1d50345773f)
85+
- 클라이언트에게 SVG 데이터 전송하는 것은 용량이 커져서 효율적이지 못했다.
86+
- viewbox 데이터와 모서리 좌표 데이터를 전송하는 방식 도입했다.
87+
- 데이터의 볼륨을 줄이고 네트워크 비용을 감소시켰다.
6788

68-
- 예매가 오픈되어 있다면 '예매하기'로 이동할 수 있습니다.
69-
- 예매가 아직 오픈되지 않았다면, '예매하기' 버튼이 비활성화 됩니다.
70-
- 이 때 서버 시간을 받아와 남은 시간을 카운트다운 해줍니다.
71-
- '예매하기'로 진입했으나 사람이 너무 많이 몰렸다면, 대기 큐 페이지로 이동해 순번을 기다리게 됩니다.
89+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/SVG-59444a6e16634fbb80cfc724215b92cf?pvs=4)
7290

73-
### 매크로 방지 확인 / 좌석 개수 선택
91+
---
92+
### 반정규화를 통한 DB 효율성 확보
7493

75-
![image](https://github.com/user-attachments/assets/944e9131-e263-474b-bb97-32fc011379fd)
94+
- 좌석 배치 데이터 저장을 위해 좌석 하나당 하나의 행을 차지했다. 또한 좌석 조회시 JOIN을 통해 데이터를 조회해야 했다.
95+
- 좌석 배치 데이터의 정적인 특성을 활용해, 저장 형태를 압축하는 반정규화를 했다.
96+
- 좌석 배치 데이터의 관리, 조회에 드는 오버헤드를 해소하였다.
7697

77-
- 진입에 성공하면 매크로 방지 문자를 입력하게 됩니다.
78-
- 그 뒤 예매할 좌석 개수를 선택해야 합니다.
79-
- 이는 우리 서비스의 특징을 악용하지 않도록 하는 중요한 과정입니다.
98+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/DB-3ff65e3d59094c08a3b3371e51a5cee5?pvs=4)
8099

81-
### 좌석 선택
100+
---
101+
### Redis 동시성 이슈 예방, 조회/수정 쿼리 성능 개선
82102

83-
![image](https://github.com/user-attachments/assets/9a3ebe8e-1f31-477a-87a4-7ed235a90420)
103+
- 좌석 현황의 저장 형태가 길어 수정/조회에 큰 오버헤드가 있었다. 또한 좌석 현황에 대한 동시적 수정/조회 때문에 동시성 이슈가 발생했다.
104+
- 좌석 현황의 저장 타입을 변환하여 부분 수정을 가능케 하고, 조회/수정 명령에는 원자성을 부여했다.
105+
- 가장 요청이 많고 핵심 병목 구간인 좌석 현황에서, 동시성을 제어하고 쿼리 성능을 개선해 병목을 완화했다.
84106

85-
- 구역을 선택합니다.
86-
- 좌석을 선택합니다.
87-
- **실시간으로 다른 사용자에 의해 선점되는 좌석을 확인할 수 있습니다.**
88-
- 좌석은 **선택과 동시에 선점**되어, 다른 사용자에게 뺏기지 않습니다.
89-
- 다시 클릭하여 선점을 해제할 수 있습니다.
90-
- 예매하기로 한 갯수만큼 선택해야 예매 확정이 가능합니다.
107+
[[자세히보기]](https://chestnut-sense-efd.notion.site/Lua-c0aa5206efa748b882c0830f5acc1c87?pvs=4)
91108

92-
### 예매 확정
109+
---
110+
### TanstackQuery의 queryKey, staleTime, invalidQueries를 이용한 API 호출 최소화
93111

94-
![image](https://github.com/user-attachments/assets/6e8fd7dc-cb4d-41aa-bf2a-6ebc4afde1ec)
112+
- 여러 페이지에서 동일한 데이터에 대한 API 반복해서 호출하고 있었다.
113+
- query키와 staleTIme설정을 통해서 캐시 데이터를 사용했다.
114+
- API 호출 감소로 서버 부하를 감소시켰다.
95115

96-
- 예매가 확정되었습니다!
97-
- 내비게이션 바의 사용자 패널에서 예매 정보를 확인/취소할 수 있습니다.
116+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/react-query-cache-api-150313ed69ba80a9abe0ff364e7e1c54?pvs=4)
98117

99-
----
118+
---
119+
### UI/UX 설계를 통한 동시 선택 문제 해결
100120

101-
# 아키텍쳐
121+
- 여러 사용자의 좌석 동시 선택으로 인한 충돌 문제가 있었다.
122+
- 클라이언트에서는 pending 상태를 표시, 서버에서 실패 응답시에는 사용자 행동을 방해하지 않으면서 실패 사실을 알렸다.
123+
- 사용자는 동시 선택 충돌이 일어나더라도 다음 행동을 방해받지 않으며, 즉시 인지하여 다른 좌석을 취할 수 있다.
102124

103-
## 기술 스택
125+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/pending-151313ed69ba808ba991d9ed32e7a2b5?pvs=4)
104126

105-
|도메인|기술 스택|
106-
|:-:|:-:|
107-
|공통| ![NPM](https://img.shields.io/badge/-npm-CB3837?logo=npm&logoColor=white) ![typescript](https://shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=white) ![ESLint](https://img.shields.io/badge/-ESLint-4B32C3?logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/-Prettier-F7B93E?logo=prettier&logoColor=white) ![Postman](https://img.shields.io/badge/-Postman-FF6C37?logo=postman&logoColor=white) |
108-
|프론트 엔드| ![Vite](https://img.shields.io/badge/-Vite-646CFF?logo=vite&logoColor=white) ![React](https://img.shields.io/badge/-React-61DAFB?logo=react&logoColor=white) ![Tailwind CSS](https://img.shields.io/badge/-Tailwind%20CSS-38B2AC?logo=tailwind-css&logoColor=white) ![Axios](https://img.shields.io/badge/-Axios-5A29E4?logo=axios&logoColor=white) ![TanStack Query](https://img.shields.io/badge/-TanStack%20Query-FF4154?logo=react-query&logoColor=white) ![ReactRouter](https://img.shields.io/badge/-React%20Router-CA4245?logo=react-router&logoColor=white) |
109-
|백엔드| ![NestJS](https://img.shields.io/badge/-NestJS-E0234E?logo=nestjs&logoColor=white) ![SSE](https://img.shields.io/badge/-SSE-000000?logo=SSE&logoColor=white) ![MySQL](https://img.shields.io/badge/MySQL-4479A1?&logo=MySQL&logoColor=white) ![Redis](https://img.shields.io/badge/redis-E34F26?&logo=Redis&logoColor=white) ![Class Validator](https://img.shields.io/badge/-Class%20Validator-000000?logo=class-validator&logoColor=white) ![Class transformer](https://img.shields.io/badge/-Class%20Transformer-000000?logo=class-transformer&logoColor=white) ![Scheduler](https://img.shields.io/badge/-Scheduler-000000?logo=scheduler&logoColor=white) ![Winston](https://img.shields.io/badge/-Winston-000000?logo=winston&logoColor=white) ![Swagger](https://img.shields.io/badge/-Swagger-85EA2D?logo=swagger&logoColor=white) |
110-
|배포|![nginx](https://img.shields.io/badge/-nginx-009639?logo=nginx&logoColor=white) ![Ncloud](https://img.shields.io/badge/-Ncloud-03C75A?logo=Ncloud&logoColor=white) ![Docker Hub](https://img.shields.io/badge/-docker%20hub-2496ED?logo=docker-hub&logoColor=white) ![Git Actions](https://img.shields.io/badge/-githubactions-2088FF?logo=githubactions&logoColor=white) |
111-
|협업 도구|![Notion](https://img.shields.io/badge/-Notion-000000?logo=notion&logoColor=white) ![GitHub](https://img.shields.io/badge/-GitHub-181717?logo=github&logoColor=white) ![Slack](https://img.shields.io/badge/-Slack-4A154B?logo=slack&logoColor=white) ![Figma](https://img.shields.io/badge/-Figma-F24E1E?logo=figma&logoColor=white) |
127+
---
128+
### 쿼리문 감소를 위한 TypeORM 최적화
112129

113-
## 아키텍처 설계도
114-
<img width="992" alt="스크린샷 2024-12-02 19 43 19" src="https://github.com/user-attachments/assets/c4c693e1-56e2-4d06-a3a9-34c0b39431c8">
130+
- 개발 편의성을 위해 도입한 TypeORM이 불필요한 쿼리 요청을 만들어내고 있었다.
131+
- 옵션을 통한 선택적 EAGER 로딩 정책으로 필요한 데이터만 불러오도록 한다.
132+
- 최소한의 쿼리로 필요한 데이터만 불러오도록 해 효율적으로 TypeORM을 사용한다.
133+
134+
[[자세히 보기]](https://chestnut-sense-efd.notion.site/TypeORM-e104a976e6be434dbecd33d48ad8f0d0?pvs=4)
115135

116-
----
117-
# 주요 기능 소개
118-
## TypeORM
119-
- [typeorm을 통한 사용한 이유와 entity 생성](https://chestnut-sense-efd.notion.site/TypeOrm-3afe7a39c14a43f09ef9d07b66ca3659?pvs=4)
120-
- [typeorm 최적화 하기](https://chestnut-sense-efd.notion.site/TypeORM-e104a976e6be434dbecd33d48ad8f0d0?pvs=4)
136+
---
137+
## 팀 1호선: 팀원 소개
138+
139+
> 모두 1호선에 살고 있어요
140+
141+
<table>
142+
<tbody>
143+
<tr>
144+
<td align="center"><a href="https://github.com/KWAKMANBO"><img src="https://github.com/user-attachments/assets/c6555879-8355-4ba3-b8c0-1ef4ad2b5d22" width="100px;" alt=""/><br /><sub><b>J05_곽희상</b></sub></a><br />1호선 당정역</td>
145+
<td align="center"><a href="https://github.com/Donggle0315"><img src="https://github.com/user-attachments/assets/f273520b-a7a9-4bae-85aa-819785192065" width="100px;" alt=""/><br /><sub><b>J033_김동현</b></sub></a><br />1호선 동암역</td>
146+
<td align="center"><a href="https://github.com/qkdl60"><img src="https://github.com/user-attachments/assets/2aefb797-9942-482b-86e5-c776b649aca6" width="100px;" alt=""/><br /><sub><b>J102_박노철</b></sub></a><br />1호선 대방역</td>
147+
<td align="center"><a href="https://github.com/kyu4583"><img src="https://github.com/user-attachments/assets/f0d1aad8-fe7c-4233-b42c-30ee7b946283" width="100px;" alt=""/><br /><sub><b>J144_신성규</b></sub></a><br />1호선 주안역</td>
148+
</tr>
149+
</tbody>
150+
</table>
121151

122-
## SSE를 통한 브로드캐스팅
123-
- [SSE 선정 이유(웹소켓, 폴링과 비교)](https://www.notion.so/WebSocket-VS-SSE-d8fed9e7c2bc46318b565dc775b6535a?pvs=4)
124-
- [SSE 브로드캐스트](https://www.notion.so/SSE-60d6e13d559a4376a7959534b26f7516?pvs=4)
125152

126-
## 좌석 현황 및 선점 관리
127-
- [좌석 현황 및 선점 관리](https://www.notion.so/6fc9fec627034269b473d43116b84f57?pvs=4)
128-
- [DB 스키마 설계](https://chestnut-sense-efd.notion.site/DB-3ff65e3d59094c08a3b3371e51a5cee5?pvs=4)
129-
- [Lua 스크립트 적용](https://chestnut-sense-efd.notion.site/Lua-c0aa5206efa748b882c0830f5acc1c87?pvs=4)
130153

131-
## 대기 큐 상태 관리
132-
- [대기 큐 정책 설계](https://chestnut-sense-efd.notion.site/8800343f85c644eca5dd1e373e4ef4b2?pvs=4)
133154

134-
## 반복적인 API 요청
135-
- [React Query Cache 기능을 이용한 데이터 공유](https://www.notion.so/24-12-02-5ee838539793492d8e5d613c11ffe67a?pvs=4#150313ed69ba804db09ed9dab85c4f1e)
136155

0 commit comments

Comments
 (0)