뒤로
한경민
·
기절초퐁 문제 해결 사례 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 검증
- 관련 Pull Request : ✅ test : 관계 정보 캐싱 성능 테스트 #347
- 위 문제 정의에 정의한 성능 저하가 예상되는 관계 정보 조회 메소드들을 기존 캐싱을 활용할 때와 매번 query 하는 방식으로 비교. 아래와 같이 뚜렷한 성능 개선 확인.
- 약 300 명의 유저들의 캐시 로드 시간 평균값이 2.10ms 정도로 측정되었다. 접속 시에만 발생하는 지연이고, 캐싱이 가져온 성능 향상에 비해 무시할 정도의 지연으로 판단.
- 메소드 별 분석
3 부작용
- 유저 접속 시 (WebSocket 연결 시) 캐싱 되는 데이터의 경우, 잦은 재접속 시 데이터가 유실되거나, 로딩 중 캐시 삭제되는 현상 발견.
- 관련 이슈
- 🐛 fix : ActivityGateway 에서 handleConnection 작업이 끝나기 전에 handleDisconnect 가 실행되는 에러 #105
- 🐛 fix : chats UI 에서 참여한 채널 리스트 데이터가 불안정한 버그 #320
- 해결책
- WebSocket handshake 의 첫 HTTP 요청 단계에서 cache loading 을 trigger 하기 위해 Io-adapter 를 활용.
- Frontend 측에서 WebSocket 연결 보장된 후 API 요청하도록 변경.
- 개선점
- 캐싱 완결 여부 flag 확인.
- 캐싱된 값이 없으면 DB 에 query 하는 fallback 구비.
4 리뷰
💡 TL;DR 요청이 많을수록 유리, 유저가 많을수록 불리
2
댓글
로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다.