기절초퐁 문제 해결 사례 1 - 유저-유저 & 유저-채팅방 관계 in-memory 캐싱
0 문제 정의

- 위와 같은 Entity Relationship 구조에서 아래와 같은 성능 저하 요인들이 있을 것으로 예상.
- 유저-유저 관계 조회에서 대부분의 UI 에서 잦게 요청된다. 매번 해당 유저가 특정 관계에서 능동적인 역할인지 혹은 수동적인 역할인지 두 경우 모두 확인이 필요하다.
- 친구 관계일 경우 해당 유저가 친구 요청 수신자인지 송신자인지 확인이 필요.
- 차단 관계일 경우 차단한 유저인지 차단 당한 유저인지 확인이 필요.
- 유저-채팅방 관계 정보
SELECT 시 JOIN 이 매번 일어난다. - 유저가 참여한 채팅방 리스트와 정보를 조회하기 위해
channels & channel_members 테이블
SELECT
cm.member_id,
cm.channel_id,
c.access_mode,
c.name, c.dm_peer_id,
(
SELECT COUNT(*)
FROM messages msg
WHERE msg.channel_id = cm.channel_id
AND msg.created_at > (
SELECT viewed_at
FROM channel_members
WHERE member_id = my_id AND channel_id = cm.channel_id
)
) AS unseen_count,
(
SELECT COUNT(*)
FROM channel_members cm2
WHERE cm2.channel_id = cm.channel_id
) AS member_count
FROM channel_members cm
LEFT JOIN channels c ON c.channel_id = cm.channel_id
WHERE cm.member_id = my_id;
- 유저가 채팅방에 입장하여 멤버들의 정보를 조회하기 위해
channels & channel_members 테이블
SELECT
cm.member_id,
cm.is_admin,
c.owner_id
FROM channel_members cm
LEFT JOIN channels c ON c.channel_id=cm.channel_id;
1 해결책
- 비즈니스 로직을 수행하기 위해 관계 정보를 조회해야하는
Service 객체들과 DB 사이에 관계 정보를 in-memory 에 캐싱하여 들고 있는 Storage 객체 구현. - 유저-유저 관계 정보를 캐싱하는
UserRelationshipStorage 객체. - 모든 유저의 정보를 항상 들고 있는 건 너무 큰 overhead 로 이어질 수 있기 때문에, 유저의 접속 상태로 캐싱 life-cycle 설정. 유저 접속 시 load & 접속 해제 시 unload.
- 유저-채팅방 관계 정보를 캐싱하는
ChannelStorage 객체. Service 객체는 관계 정보 CRUD 시 반드시 Storage 객체를 거친다.
💡 Dependency Injection 패턴의 NestJs 에서 주입된 의존 객체가 Singleton 으로 유지되기 때문에 가능한 해결책이었다.
2 검증

- 약 300 명의 유저들의 캐시 로드 시간 평균값이 2.10ms 정도로 측정되었다. 접속 시에만 발생하는 지연이고, 캐싱이 가져온 성능 향상에 비해 무시할 정도의 지연으로 판단.
- 메소드 별 분석

3 부작용
4 리뷰
💡 TL;DR 요청이 많을수록 유리, 유저가 많을수록 불리
