팀 프로젝트 3주차 회고

찬디·2025년 8월 9일

우테코

목록 보기
12/18

개요

우테코 팀 프로젝트 3주차를 진행하면서 느꼈던 것들을 회고해본다.

무슨 일이 있었나?

  • 5분만에 500개의 알람을 보내는 경험
  • 5분만에 request 1000개가 오는 경험
  • 11만개의 request와 톰캣 쓰레드 max를 찍은 경험
    실시간으로 모니터링을 통해 분석해본 경험

이벤트 리스너로 강결합 분리시도

  • 이벤트 시작일 ~ 종료일까지 조회수 확인 기능을 추가했다.
    • 이벤트 수정시에 이벤트 시작~종료일이 바뀔 수 있게 되면서 조회수 기능도 함께 바뀌는 요구사항이 생겼다.
      EventService에서 이벤트 조회수까지 변경관리 하게 되면서 책임이 매우 커졌다. 테스트코드만 해도 300줄이 넘어가기 시작했다.

이벤트 리스너로 강결합 분리하자

  • 이벤트 서비스는 이벤트가 수정 되었다는 이벤트만 출력하게하자. 이러면 적어도 이벤트 서비스에서 이벤트 수정에 대한 행위는 알 필요가 없어졌다

분리는 했지만 영향은 준다

  • 이벤트 조회수가 만들어지지 않으면 이벤트 조회시에 NPE가 터진다.
    • 이벤트 조회수 때문에 이벤트 그 자체의 기능에 문제가 생기는 것이다.
    • 그런데, 이것은 커버가 안되는 문제에 대해서 예상을못했던거지 이 방식 자체의 설계가 문제인것은 아니다.

이벤트 기반의 느슨하게 결합된 시스템(Loosely Coupled System)에서는 각 컴포넌트가 '서로를 완벽히 신뢰하지 않는 것'을 전제로 방어적으로 설계되어야 한다.

EventService는 ViewsService가 언제나 즉시 완벽하게 데이터를 만들 것이라고 믿어서는 안 되고
ViewsService는 EventService가 항상 올바른 데이터를 담은 이벤트를 발행할 것이라고 믿어서는 안된다.

대응쿼리 날리는 경험

  • 이벤트 조회수 기능이 추가되며 과거에 생겼던 이벤트에 대해서도 이벤트 조회수가 추가될 필요성이 생겼다.
    • 그런데 기존 이벤트에는 이벤트 조회수가 없으므로 조회수 출력시에 NPE가 출력된다.
    • 이벤트 조회 요청을 보내면 NPE로 인해 조회조차 되지 않는 문제가 테스트에서 발견됐다.
  • 임시 방편으로 릴리즈가 나간뒤 바로 대응쿼리를 날리는것으로 해결했다.

마이그레이션을 위한 flyway

INSERT INTO event_view_metric (event_statistic_id, view_date, view_count, created_at, updated_at)  
SELECT  
    DateSeries.event_statistic_id,  
    DateSeries.metric_date,  
    0,  
    NOW(),  
    NOW()  
FROM  
    (  
        -- 재귀 쿼리 부분을 FROM 절의 서브쿼리로 이동  
        WITH RECURSIVE DateSeries_CTE AS (  
            SELECT  
                es.event_statistic_id,  
                CAST(e.registration_start AS DATE) AS metric_date,  
                CAST(e.event_end AS DATE) AS end_date  
            FROM  
                event e  
                    JOIN  
                event_statistic es ON e.event_id = es.event_id  
            UNION ALL  
            SELECT                ds.event_statistic_id,  
                DATE_ADD(ds.metric_date, INTERVAL 1 DAY),  
                ds.end_date  
            FROM  
                DateSeries_CTE ds  
            WHERE  
                ds.metric_date < ds.end_date  
        )  
        SELECT * FROM DateSeries_CTE  
    ) AS DateSeries  
WHERE NOT EXISTS (  
    SELECT 1  
    FROM event_view_metric evm  
    WHERE evm.event_statistic_id = DateSeries.event_statistic_id  
      AND evm.view_date = DateSeries.metric_date  
);

위와 같이 항상 대응쿼리를 수동으로 날릴 수 없다. 그래서 flyway를 도입하나보다..

request 11만개가 왔을때 대응하기

  • 데모데이중, 코치분이 갑자기 비동기로 디도스와 비슷한 방식의 무한 요청을 보내왔다..
    그결과

갑자기 톰캣 스레드 풀이 max가 찍혔다

무엇이 문제인지 설명해봐라

  1. 스레드 풀이 max로 찍힘
  2. RDS 커넥션에는 문제 없음
  3. CPU 점유율도 문제 없음

쓰레드 관련 문제라면, 대응 어떻게 할것인가

  • 임시 방편으로는 max 쓰레드풀을 임시로 늘린다. 하지만 컨텍스트 스위칭을 생각하면 무조건 정답이 아니다.
    • 결국 분산할 수 밖에 없다.
    • rb를 도입할때가 온것같다.
    • 임시방편으로 쓰레드 풀을 늘리고싶어도, 무중단 배포가 되지 않아 서버를 껐다킬수없었다.
      • 무중단 배포도 해야한다.
  • 지금은 rds 문제가 크게 없지만, 나중에는 조회, 쓰기 디비를 분리해야할지도 모른다.

로드밸런서 넣고 싶지만 돈이 없어

  • aws 제공 rb는 비싸다.
    • 트래픽마다 rb 비용이 청구되는것은 디도스 공격시에 치명적이다.
    • 남은 선택지 중 할 수 있는 것은 애플리케이션방식의 rb일 것이다.

애플리케이션 방식의 rb 고려할 부분

  • 해시 값을 잘 분리해서 잘 분배해야한다.
    • 해시 값은 분리가 잘 되어도 부하별로 나뉘지 않으면 의미가 없다.

이제 뭐할까

전반적인 서비스의 기능 개발은 마무리되어간다.
이제 서비스를 지속적으로 운영가능하게 고도화할 때가 온것같다.

또한 코치분이 다음에 큰 이벤트를 열 예정이라고한다.

  • 이것만으로 가용성을 고려할 가치가 있다.
  • 알람 서버를 분리하자
  • 알람 배치를 적용해야할까?
  • 알람이 중복되더라도 '무조건' 유저에게 닿도록하자
profile
깃허브에서 velog로 블로그를 이전했습니다.

0개의 댓글