뒤로
한경민
한경민 ·

기절초퐁 문제 해결 사례 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 부작용

4 리뷰

💡 TL;DR 요청이 많을수록 유리, 유저가 많을수록 불리


2

댓글

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.