|
37 | 37 |
|
38 | 38 | ### Server Sent Event를 통한 서버 리소스 절약 |
39 | 39 |
|
| 40 | +<div style="display: flex; gap: 0;"> |
| 41 | + <img src="https://github.com/user-attachments/assets/148dc6c0-b2e7-4d1b-a1d6-8a341d2e80ee" style="width: 70%;"/> |
| 42 | + <img src="https://github.com/user-attachments/assets/3207f889-d086-4648-b80c-6bc5bccb4c84" style="width: 70%;"/> |
| 43 | +</div> |
| 44 | + |
40 | 45 | - 기존에 선택한 WebSocket을 통한 실시간 통신은 서버가 클라이언트의 요청을 계속해서 수신해야 하기에 서버의 리소스 부담으로 이어진다. |
41 | 46 | - 서버가 클라이언트에게 일방적으로 데이터를 푸시하는 Server Sent Event로 실시간 통신 방식을 변경한다. |
42 | 47 | - 서버가 더 이상 클라이언트 요청 수신에 대한 리소스를 낭비하지 않게 된다. |
|
46 | 51 | --- |
47 | 52 | ### 데이터 발행 비용 절약을 위한 SSE 브로드캐스팅 |
48 | 53 |
|
| 54 | +<div style="display: flex; gap: 0;"> |
| 55 | + <img src="https://github.com/user-attachments/assets/ac9b0141-d688-422c-a6f2-3d80bac313c1" style="width: 70%;"/> |
| 56 | + <img src="https://github.com/user-attachments/assets/b347e44a-1fe5-48de-9098-7906b16940ec" style="width: 70%;"/> |
| 57 | +</div> |
| 58 | + |
49 | 59 | - 기존 방식은 클라이언트마다 각각 데이터가 발행돼, 구독자가 늘어남에 따라 오버헤드가 커지는 문제가 있었다. |
50 | 60 | - 동일한 데이터 소스를 여러 클라이언트가 참조하는 형태의 브로드캐스팅을 구현했다. |
51 | 61 | - 구독자의 수에 상관 없이 데이터 발행에 드는 비용을 고정적으로 줄일 수 있었다. |
|
64 | 74 | --- |
65 | 75 | ### 상태 관리와 추적을 통한 대기열 누수 방지 |
66 | 76 |
|
| 77 | +<div style="display: flex; gap: 0;"> |
| 78 | + <img src="https://github.com/user-attachments/assets/5a0bff85-cf15-49fe-bcc8-eeb0a352b50b" style="width: 70%;"/> |
| 79 | + <img src="https://github.com/user-attachments/assets/08176f3f-808b-450d-9517-ceaa32d087e2" style="width: 70%;"/> |
| 80 | +</div> |
| 81 | + |
67 | 82 | - 대기열 화면과 좌석 선택 화면 사이에서 클라이언트의 갖은 예외적 움직임이 발생했고, 대기열 운영에 오류를 일으켰다. |
68 | 83 | - 변수를 통제하는 방향으로 유저의 상태를 세분하게 정의, 추적, 관리했다. |
69 | 84 | - 예외적 상황에도 누수 없이 정확한 인원 추적으로 서비스의 무결성을 유지할 수 있게 되었다. |
|
73 | 88 | --- |
74 | 89 | ### 매크로 방지 문자를 통해 부하 분산 |
75 | 90 |
|
| 91 | +<div style="display: flex; gap: 0;"> |
| 92 | + <img src="https://github.com/user-attachments/assets/7146aa41-9eed-46ed-b59e-12f875fc2363" style="width: 70%;"/> |
| 93 | + <img src="https://github.com/user-attachments/assets/fd02af81-48e3-4ce6-a04a-b38a138de3e8" style="width: 70%;"/> |
| 94 | +</div> |
| 95 | + |
76 | 96 | - 예매 오픈 시 다수의 클라이언트가 동시에 좌석 점유 페이지로 이동해 서버에 과도한 부하가 발생한다. |
77 | 97 | - 매크로 방지 기능을 사용해 사용자 입력을 요구하여 의도적으로 지연을 발생시킨다. |
78 | 98 | - 동시에 들어오는 요청을 분산시켜 서버의 안정성을 확보한다. |
|
82 | 102 | --- |
83 | 103 | ### 좌표 기반 데이터로 통신 비용 절약 |
84 | 104 |
|
| 105 | +<div style="display: flex; gap: 0;"> |
| 106 | + <img src="https://github.com/user-attachments/assets/c3885644-2982-455b-b74f-0498af65acdd" style="width: 70%;"/> |
| 107 | + <img src="https://github.com/user-attachments/assets/8a26192e-b92a-4e88-9731-e335722f8e90" style="width: 70%;"/> |
| 108 | +</div> |
| 109 | + |
85 | 110 | - 클라이언트에게 SVG 데이터 전송하는 것은 용량이 커져서 효율적이지 못했다. |
86 | 111 | - viewbox 데이터와 모서리 좌표 데이터를 전송하는 방식 도입했다. |
87 | 112 | - 데이터의 볼륨을 줄이고 네트워크 비용을 감소시켰다. |
|
91 | 116 | --- |
92 | 117 | ### 반정규화를 통한 DB 효율성 확보 |
93 | 118 |
|
| 119 | +<div style="display: flex; gap: 0;"> |
| 120 | + <img src="https://github.com/user-attachments/assets/14dc5243-45d5-43f5-ba12-3f1839a37181" style="width: 70%;"/> |
| 121 | + <img src="https://github.com/user-attachments/assets/0479f029-6c01-4be3-8de0-5a36d480474b" style="width: 70%;"/> |
| 122 | + <img src="https://github.com/user-attachments/assets/8a4374de-b667-424c-8a68-ab580ee824c5" style="width: 70%;"/> |
| 123 | +</div> |
| 124 | + |
94 | 125 | - 좌석 배치 데이터 저장을 위해 좌석 하나당 하나의 행을 차지했다. 또한 좌석 조회시 JOIN을 통해 데이터를 조회해야 했다. |
95 | 126 | - 좌석 배치 데이터의 정적인 특성을 활용해, 저장 형태를 압축하는 반정규화를 했다. |
96 | 127 | - 좌석 배치 데이터의 관리, 조회에 드는 오버헤드를 해소하였다. |
|
100 | 131 | --- |
101 | 132 | ### Redis 동시성 이슈 예방, 조회/수정 쿼리 성능 개선 |
102 | 133 |
|
| 134 | +<div style="display: flex; gap: 0;"> |
| 135 | + <img src="https://github.com/user-attachments/assets/4cfe7fea-405f-4a18-9107-2977c79b1bec" style="width: 70%;"/> |
| 136 | + <img src="https://github.com/user-attachments/assets/e5b36e55-635a-4775-9066-d754896ecd49" style="width: 70%;"/> |
| 137 | +</div> |
| 138 | + |
103 | 139 | - 좌석 현황의 저장 형태가 길어 수정/조회에 큰 오버헤드가 있었다. 또한 좌석 현황에 대한 동시적 수정/조회 때문에 동시성 이슈가 발생했다. |
104 | 140 | - 좌석 현황의 저장 타입을 변환하여 부분 수정을 가능케 하고, 조회/수정 명령에는 원자성을 부여했다. |
105 | 141 | - 가장 요청이 많고 핵심 병목 구간인 좌석 현황에서, 동시성을 제어하고 쿼리 성능을 개선해 병목을 완화했다. |
|
109 | 145 | --- |
110 | 146 | ### TanstackQuery의 queryKey, staleTime, invalidQueries를 이용한 API 호출 최소화 |
111 | 147 |
|
| 148 | +<div style="display: flex; gap: 0;"> |
| 149 | + <img src="https://github.com/user-attachments/assets/1c79c967-596e-49e4-a676-903d4ba66f26" style="width: 70%;"/> |
| 150 | + <img src="https://github.com/user-attachments/assets/d0142c11-dd70-4064-9d78-b959c1810bfe" style="width: 70%;"/> |
| 151 | +</div> |
| 152 | + |
112 | 153 | - 여러 페이지에서 동일한 데이터에 대한 API 반복해서 호출하고 있었다. |
113 | 154 | - query키와 staleTIme설정을 통해서 캐시 데이터를 사용했다. |
114 | 155 | - API 호출 감소로 서버 부하를 감소시켰다. |
|
118 | 159 | --- |
119 | 160 | ### UI/UX 설계를 통한 동시 선택 문제 해결 |
120 | 161 |
|
| 162 | +<div style="display: flex; gap: 0;"> |
| 163 | + <img src="https://github.com/user-attachments/assets/22f0e213-aac3-4e44-a292-25c3b1098320" style="width: 70%;"/> |
| 164 | + <img src="https://github.com/user-attachments/assets/3c005bea-c50b-416f-9ddb-3e947fae030a" style="width: 70%;"/> |
| 165 | +</div> |
| 166 | + |
121 | 167 | - 여러 사용자의 좌석 동시 선택으로 인한 충돌 문제가 있었다. |
122 | 168 | - 클라이언트에서는 pending 상태를 표시, 서버에서 실패 응답시에는 사용자 행동을 방해하지 않으면서 실패 사실을 알렸다. |
123 | 169 | - 사용자는 동시 선택 충돌이 일어나더라도 다음 행동을 방해받지 않으며, 즉시 인지하여 다른 좌석을 취할 수 있다. |
|
127 | 173 | --- |
128 | 174 | ### 쿼리문 감소를 위한 TypeORM 최적화 |
129 | 175 |
|
| 176 | +<div style="display: flex; gap: 0;"> |
| 177 | + <img src="https://github.com/user-attachments/assets/c1e64524-2cd9-4c50-aa62-710936213188" style="width: 70%;"/> |
| 178 | + <img src="https://github.com/user-attachments/assets/cbb79390-993c-41b4-97a0-3417d12b5d01" style="width: 70%;"/> |
| 179 | +</div> |
| 180 | + |
130 | 181 | - 개발 편의성을 위해 도입한 TypeORM이 불필요한 쿼리 요청을 만들어내고 있었다. |
131 | 182 | - 옵션을 통한 선택적 EAGER 로딩 정책으로 필요한 데이터만 불러오도록 한다. |
132 | 183 | - 최소한의 쿼리로 필요한 데이터만 불러오도록 해 효율적으로 TypeORM을 사용한다. |
|
0 commit comments