-
Notifications
You must be signed in to change notification settings - Fork 1
๐ก Redis pubsub๋ฅผ ํ์ฉํ SSE ์ ์ฉ๊ธฐ
์๋ ! ํด๋ก๋ฐํํธ๋ผ! ๋ ์ฌ์ฉ์๋ค์ด ์์ฑ์ผ๋ก ์์ ๊ณผ ๋ฐ์์ ๊ฒจ๋ฃจ๋ ๊ฒ์์ ์ฆ๊ธธ ์ ์๋๋ก ์ค๊ณ๋ ๊ฒ์์ ๋๋ค. ๐ค WebRTC๋ฅผ ํตํด ์์ฑ์ ์คํธ๋ฆฌ๋ฐํ๊ณ , WebSocket์ ์ฌ์ฉํ์ฌ ์ค์๊ฐ ํต์ ์ ์ง์ํฉ๋๋ค. ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ , ๊ฒ์ ๋ฐฉ ๋ชฉ๋ก์ ์ค์๊ฐ ์ ๋ฐ์ดํธ๋ ์ฌ์ ํ ๋ฌธ์ ์์ฃ . ๋ฐฉ์ ์์ฑ, ์ฌ์ฉ์ ์ฐธ์ฌ, ํด์ฅ ๋ฑ์ ๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ๋ฐ์ํ๋ ค๋ฉด, ๋ ๋์ ๋ฐฉ๋ฒ์ด ํ์ํ์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Redis Pub/Sub์ Server-Sent Events๋ฅผ ๋์ ํ์ฌ ๊ฒ์ ๋ฐฉ ๋ชฉ๋ก์ ์ค์๊ฐ ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ ๊ฒฝํ์ ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค. ๐
๊ธฐ์กด์๋ HTTP REST API๋ฅผ ํตํด ๊ฒ์ ๋ฐฉ ๋ชฉ๋ก์ ๊ด๋ฆฌํ์ต๋๋ค. ์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ ๋๋ง๋ค ์๋ฒ์์ ๋ฐฉ ๋ชฉ๋ก์ ์ ๋ฌํ๋ ๋ฐฉ์์ด์์ฃ . ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์๋ ๋ช ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
1๏ธโฃ ์ค์๊ฐ์ฑ ๋ถ์กฑ
- ๋ฐฉ์ด ์์ฑ๋๊ฑฐ๋ ์ฌ์ฉ์๊ฐ ๋ฐฉ์ ๋ ๋๋ฉด, ์ฆ๊ฐ์ ์ผ๋ก ๋ค๋ฅธ ์ฌ์ฉ์์๊ฒ ๋ฐ์๋์ง ์์์ต๋๋ค. ์ด๋ก ์ธํด ์ฌ์ฉ์ ๊ฒฝํ์ ์ง์ฐ์ด ๋ฐ์ํ์ต๋๋ค.
2๏ธโฃ ์๋ฒ ๋ถํ ์ฆ๊ฐ
- ํด๋ผ์ด์ธํธ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ฐฉ์์ผ๋ก ๊ฐฑ์ ๋๋ ๊ตฌ์กฐ์ฌ์ ์๋ฒ์ ๋ถํ์ํ ๋ถํ๋ฅผ ์ด๋ํ์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Redis Pub/Sub์ SSE๋ฅผ ๋์ ํ์ฌ ์ค์๊ฐ์ฑ๊ณผ ์๋ฒ ํจ์จ์ฑ์ ๊ฐ์ ํ์ต๋๋ค. ๐
Redis Pub/Sub๋ Publish/Subscribe(๋ฐํ/๊ตฌ๋ ) ํจํด์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ๋ฉ์์ง ์์คํ ์ ๋๋ค. ๋ฐํ์๋ ๋ฉ์์ง๋ฅผ ํน์ ์ฑ๋์ ๋ณด๋ด๊ณ , ๊ตฌ๋ ์๋ ํด๋น ์ฑ๋์ ๋ฉ์์ง๋ฅผ ์ค์๊ฐ์ผ๋ก ์์ ํ ์ ์์ต๋๋ค.
- Publisher(๋ฐํ์): ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ ์ญํ ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๊ฒ์ ๋ฐฉ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
- Subscriber(๊ตฌ๋ ์): ํน์ ์ฑ๋์ ๊ตฌ๋ ํ๊ณ , ๊ทธ ์ฑ๋์ ๋ฉ์์ง๊ฐ ๋ค์ด์ค๋ฉด ์ค์๊ฐ์ผ๋ก ์ด๋ฅผ ์์ ํฉ๋๋ค.
Redis ๊ณต์ ๋ฌธ์์์๋ Pub/Sub๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ค๋ช ํฉ๋๋ค:
"Redis Pub/Sub is a simple and fast messaging system. Messages are sent by publishers to channels, and subscribers receive those messages when subscribed to the corresponding channels."
Redis์์ Pub/Sub๋ฅผ ํ์ฉํ๋ฉด ์ฌ๋ฌ ์๋ฒ๋ ์์คํ ๊ฐ ์ค์๊ฐ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ์กํ ์ ์์ต๋๋ค. ์ด๋ก์จ ๊ฒ์ ๋ฐฉ ์ํ์ ์ค์๊ฐ ๋๊ธฐํ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
SSE๋ ์๋ฒ-ํด๋ผ์ด์ธํธ ๊ฐ์ ๋จ๋ฐฉํฅ ํต์ ์ ์ํ ๊ธฐ์ ๋ก, ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์ฐ๊ฒฐ์ ์ ์งํ๊ณ ์๋ฒ๊ฐ ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํธ์ํ๋ ๋ฐฉ์์ ๋๋ค. WebSocket๊ณผ ๋ฌ๋ฆฌ ์๋ฐฉํฅ ํต์ ์ด ํ์ ์๋ ์ํฉ์์ ๊ฐํธํ๊ณ ํจ์จ์ ์ธ ์๋ฃจ์ ์ ์ ๊ณตํฉ๋๋ค.
SSE๋ HTTP ํ๋กํ ์ฝ์ ๊ธฐ๋ฐ์ผ๋ก ์๋ํ๋ฏ๋ก ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ด ๋ฐ์ด๋๋ฉฐ ์ค์ ์ด ๊ฐ๋จํฉ๋๋ค. ์ด๋ ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ํธ์ํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
- ๊ฒ์ ๋ฐฉ์ด ์์ฑ๋๊ฑฐ๋ ์ญ์ ๋๊ฑฐ๋ ์ฌ์ฉ์๊ฐ ์ฐธ์ฌ/ํด์ฅํ๋ ๊ฒฝ์ฐ, Redis์ Pub/Sub๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ๋ณ๊ฒฝ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
- ์๋ฒ๋ ์ด ๋ฉ์์ง๋ฅผ ๊ตฌ๋ ํ์ฌ ๋ฐฉ ์ํ ๋ณ๊ฒฝ์ ๊ฐ์งํฉ๋๋ค.
- ์๋ฒ๋ Redis์์ ๋ฉ์์ง๋ฅผ ์์ ํ๊ณ , ์ด๋ฅผ SSE๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์ ์ ๋ฌํฉ๋๋ค.
- ํด๋ผ์ด์ธํธ๋ ์์ ํ ์ด๋ฒคํธ๋ก UI๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธํฉ๋๋ค.
์ด ๊ตฌ์กฐ๋ ์ค์๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ํต์ฌ์ด ๋ฉ๋๋ค.
Redis์ Pub/Sub ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด ์ฌ๋ฌ ์์คํ ๊ฐ์ ์ค์๊ฐ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. ์ด๋, Publish(๋ฐํ)์ Subscribe(๊ตฌ๋ ) ์ญํ ์ ๋ถ๋ฆฌํ๋ฉด ์ฑ๋ฅ๊ณผ ์ ์ง๋ณด์ ์ธก๋ฉด์์ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค.
Redis Pub/Sub์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
Redis์์ Pub/Sub ๋ชจ๋ธ์ ๋ ๊ฐ์ง ์ฃผ์ ์ญํ ๋ก ๋๋ฉ๋๋ค:
- Publisher (Pub): ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ ํด๋ผ์ด์ธํธ์ ๋๋ค. ํน์ ์ฑ๋์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ฉด, ์ด ์ฑ๋์ ๊ตฌ๋ ํ๊ณ ์๋ ํด๋ผ์ด์ธํธ๋ค์ด ํด๋น ๋ฉ์์ง๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
- Subscriber (Sub): ํน์ ์ฑ๋์ ๊ตฌ๋ ํ๊ณ ์๋ ํด๋ผ์ด์ธํธ์ ๋๋ค. ๊ตฌ๋ ํ ์ฑ๋์ ๋ฉ์์ง๊ฐ ๋๋ฌํ๋ฉด ์ด๋ฅผ ์ค์๊ฐ์ผ๋ก ์์ ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ์ Redis ํด๋ผ์ด์ธํธ๋ฅผ ์ฌ์ฉํด Pub/Sub๋ฅผ ์ฒ๋ฆฌํ ์ ์์ง๋ง, ์ค์ ๋ก ํด๋ผ์ด์ธํธ๋ฅผ ๋ถ๋ฆฌํ์ฌ ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ ์ญํ ์ ์ํํ๋๋ก ์ค๊ณํ๋ฉด ๋ ํจ์จ์ ์ด๊ณ , ์ ์ง๋ณด์๊ฐ ์ฌ์ด ์์คํ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
Pub/Sub ํด๋ผ์ด์ธํธ ๋ถ๋ฆฌ์ ์ด์
Redis ๊ณต์ ๋ฌธ์์์๋ Pub/Sub ์์คํ ์ ํตํด ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ๋ฉ์์ง๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฐ์ ์ ์๋ค๊ณ ์ธ๊ธํฉ๋๋ค. ๋ํ, Pub/Sub ํจํด์ ์ฌ์ฉํ ๋, ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ๋ฉด ๋ ํจ์จ์ ์ผ๋ก ์์คํ ์ ํ์ฅํ ์ ์์ต๋๋ค.
"Using separate clients for publishing and subscribing can help improve performance in scenarios where a client might need to do other work in parallel, such as managing a user interface while receiving messages."
๋ฐ๋ผ์ Pub/Sub ํด๋ผ์ด์ธํธ๋ฅผ ๋ถ๋ฆฌํ๋ฉด ์ฑ๋ฅ ๊ฐ์ ๊ณผ ํจ๊ป ์ ์ง๋ณด์์ฑ์ด ํฅ์๋๋ฉฐ, ๊ฐ ํด๋ผ์ด์ธํธ๊ฐ ์์ ์ ์ญํ ์ ๋ง๊ฒ ์ต์ ํ๋ ์์ ์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, Publisher๋ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ ๋ฐ ์ง์คํ๊ณ , Subscriber๋ ์์ ๋ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ์ง์คํ๊ฒ ๋ฉ๋๋ค.
@Injectable()
export class RedisService implements OnModuleDestroy {
private pubClient: Redis; // publish ํด๋ผ์ด์ธํธ
private subClient: Redis; // subscribe ํด๋ผ์ด์ธํธ
constructor(@Inject('REDIS_CLIENT') private readonly redisClient: Redis) {
this.pubClient = new Redis(redisClient.options);
this.subClient = new Redis(redisClient.options);
}
onModuleDestroy() {
this.pubClient.quit();
this.subClient.quit();
}
...
}
๊ฒ์ ๋ฐฉ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค roomUpdate
์ฑ๋์ ํตํด ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฐฉ์ด ์์ฑ๋ ๋ ๋ค์๊ณผ ๊ฐ์ด Redis์ ๋ฉ์์ง๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
await this.redisService.publishToChannel(
"roomUpdate",
JSON.stringify(roomData)
);
์ด ๋ฉ์์ง๋ roomUpdate
์ฑ๋์ ๊ตฌ๋
ํ๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ์๊ฒ ์ค์๊ฐ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
์๋ฒ๋ roomUpdate
์ฑ๋์ ๊ตฌ๋
ํ๊ณ , ๊ฒ์ ๋ฐฉ์ ์ํ ๋ณํ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํฉ๋๋ค. RoomController
์์๋ RedisService
๋ฅผ ํตํด ์ฑ๋์ ๊ตฌ๋
ํ๊ณ , roomUpdateSubject
๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ์ฌ SSE๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ์ค์๊ฐ์ผ๋ก ์
๋ฐ์ดํธ๋ฅผ ์ ์กํฉ๋๋ค.
export class RoomController {
private readonly roomUpdateSubject = new Subject<MessageEvent>();
constructor(private readonly redisService: RedisService) {
this.redisService.subscribeToChannel("roomUpdate", async (message) => {
const roomKeys = await this.redisService.keys("room:*");
const rooms = await Promise.all(
roomKeys.map(async (key) => {
const roomData = await this.redisService.get<string>(key);
return JSON.parse(roomData) as RoomDataDto;
})
);
this.roomUpdateSubject.next({ data: rooms });
});
}
@Sse("stream")
getRoomUpdates(): Observable<MessageEvent> {
return this.roomUpdateSubject.asObservable();
}
}
ํด๋ผ์ด์ธํธ๋ EventSource
API๋ฅผ ์ฌ์ฉํ์ฌ SSE๋ฅผ ํตํด ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ ํฉ๋๋ค. ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ ์ฐ๊ฒฐ ํ, ์ค์๊ฐ์ผ๋ก ์ ์ก๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ UI๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
const eventSource = new EventSource("/api/rooms/stream");
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
updateRoomList(update); // UI ์
๋ฐ์ดํธ
};
WebSocket์ ์๋ฐฉํฅ ํต์ ์ ์ ์ฉํ์ง๋ง, ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ ์ ์ก๋ง ํ์ํ ๋๋ SSE๊ฐ ๋ ๊ฐ๋จํ๊ณ ํจ์จ์ ์ ๋๋ค.
SSE๋ HTTP ๊ธฐ๋ฐ์ด๋ฏ๋ก ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์๋๋ฉฐ, ์ค์ ์ด ๊ฐ๋จํฉ๋๋ค. ๋ฐ๋ฉด, WebSocket์ ๋ฐฉํ๋ฒฝ์ด๋ ๋คํธ์ํฌ ์ค์ ์ ๋ฐ๋ผ ์ฐจ๋จ๋ ์ ์์ต๋๋ค.
WebSocket์ ๋ชจ๋ ํด๋ผ์ด์ธํธ์์ ์ฐ๊ฒฐ์ ์ ์งํด์ผ ํ๋ฏ๋ก, ๋ค์์ ํด๋ผ์ด์ธํธ์๊ฒ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ๋ SSE๊ฐ ๋ ํจ์จ์ ์ ๋๋ค.
1๏ธโฃ ์ค์๊ฐ ๋ฐฉ ๋ชฉ๋ก ์ ๋ฐ์ดํธ
- ๋ฐฉ ์์ฑ, ์ญ์ , ์ฌ์ฉ์์ ์ด๋ ๋ฑ์ด ์ฆ์ ๋ฐ์๋์ด ์ฌ์ฉ์ ๊ฒฝํ์ด ํฌ๊ฒ ํฅ์๋์์ต๋๋ค.
2๏ธโฃ ์๋ฒ ํจ์จ์ฑ ํฅ์
- ๊ธฐ์กด์ HTTP REST ๋ฐฉ์์์๋ ํด๋ผ์ด์ธํธ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด์ผ ํ์ง๋ง, SSE์ Redis Pub/Sub๋ฅผ ํตํด ์๋ฒ๋ ํ์ํ ๊ฒฝ์ฐ์๋ง ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ณ , ํด๋ผ์ด์ธํธ๋ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋ฅผ ๋ฐ์ ์ ์๊ฒ ๋์์ต๋๋ค.
Redis Pub/Sub์ SSE๋ฅผ ํ์ฉํ ๊ฒ์ ๋ฐฉ ๋ชฉ๋ก์ ์ค์๊ฐ ์ ๋ฐ์ดํธ ์์คํ ์ ์๋ ! ํด๋ก๋ฐํํธ๋ผ! ๊ฒ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์์ผฐ์ต๋๋ค. ์์ผ๋ก๋ ๋ค์ํ ์ค์๊ฐ ๊ธฐ์ ์ ํ์ฉํ์ฌ ๋ ๋์ ์๋น์ค๋ฅผ ์ ๊ณตํ ์ ์๋๋ก ๋ ธ๋ ฅํ๊ฒ ์ต๋๋ค. ๐
์ฐธ๊ณ ์๋ฃ
- Redis Pub/Sub ๊ณต์ ๋ฌธ์: Redis Pub/Sub
- SSE ๊ณต์ ๋ฌธ์: MDN Web Docs - SSE
๐ค Ground Rule
๐ Convention
๐ณ Git Branch ์ ๋ต
๐ ๏ธ AGT - Automatic Git & Github Tool
๐ WebRTC Mesh โ ํธ๋ํฝ ๊ณ์ฐ
๐ข WebRTC Mesh - ํ๋ํ ์ฌ์
๐ฌ WebRTC๋ฅผ ์์๋ณด์
๐ฎ SSE(Server Sent Events)
๐ SSE Pagination
โณ Socket ํต์ ์์ ๋น๋๊ธฐ ์์
์์ ๋ณด์ฅ ๋ฐฉ๋ฒ
๐ก Redis pub/sub๋ฅผ ํ์ฉํ SSE ์ ์ฉ๊ธฐ
๐๏ธ Naver Cloud Platform์ ํ์ฉํ ๋ฐฐํฌ ์ ๋ต
โ๏ธ๐ ๋ถํ ํ
์คํธ: ๋จ์ผ ์ธ์คํด์ค VS NKS
๐ดโโ๏ธ Redis๋ก ๊ฒ์๋ฐฉ ๊ด๋ฆฌ ์ต์ ํ: ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์ฑ๋ฅ ๊ฐ์
๐ ํ์๋ก ์บ๋ฆฐ๋
๐ค ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ ํ
ํ๋ฆฟ
๐ค ํ๊ณ ํ
ํ๋ฆฟ
0๏ธโฃ 0์ฃผ์ฐจ ๋ฉํ ๋ง ์ผ์ง
1๏ธโฃ 1์ฃผ์ฐจ ๋ฉํ ๋ง ์ผ์ง
2๏ธโฃ 2์ฃผ์ฐจ ๋ฉํ ๋ง ์ผ์ง
3๏ธโฃ 3์ฃผ์ฐจ ๋ฉํ ๋ง ์ผ์ง
4๏ธโฃ 4์ฃผ์ฐจ ๋ฉํ ๋ง ์ผ์ง
0๏ธโฃ 0์ฃผ์ฐจ ๋ฐํ
1๏ธโฃ 1์ฃผ์ฐจ ๋ฐํ
2๏ธโฃ 2์ฃผ์ฐจ ๋ฐํ
3๏ธโฃ 3์ฃผ์ฐจ ๋ฐํ
4๏ธโฃ 4์ฃผ์ฐจ ๋ฐํ
5๏ธโฃ ์ต์ข
๋ฐํ