Post

피드시스템 안정성 향상기 파해치기

피드시스템 안정성 향상기 파해치기

당근 피드시스템 안정성 향상기를 읽고 — 마이크로서비스인데 왜 연쇄장애가 날까?

참고한 아티클: 당근 피드시스템 안정성 향상기: 프로파일링부터 서킷브레이커까지

개선 사항 들어가기 전 가졌던 의문

피드시스템이 복잡한 의존성 구조라는 건 알겠는데, 왜 그중 하나라도 문제가 생기면 연쇄적으로 장애가 발생하는 구조일까?

마이크로서비스라면 느슨한 결합이어야 할 텐데, 왜 하나가 죽으면 같이 죽는 구조로 되어 있을까?


내 질문에 대한 답:

결론부터 정리하면, 연쇄장애의 본질은 이거였다.

배포 단위의 느슨한 결합 ≠ 런타임의 느슨한 결합

마이크로서비스로 쪼개면 개발과 배포는 분리된다. 하지만 피드를 만들 때는 여러 서비스를 동기로 호출하고 응답을 기다린다.

여기서 연쇄장애가 발생하는 실제 메커니즘:

  1. 추천 서비스(없어도 되는 선택 서비스)가 느려진다
  2. 피드 서버가 그 응답을 타임아웃까지 기다리며 자원을 점유한다
  3. 요청이 계속 쌓이면 피드 서버의 자원이 고갈된다
  4. 정작 멀쩡한 필수 서비스 요청까지 처리하지 못하고 전체가 다운된다

즉, 없어도 되는 선택 서비스 하나가 자원을 빨아먹어 전체를 죽이는 것이 진짜 문제였다.


메모리 동적 할당 최적화

왜 연결리스트를 안 썼을까?

처음엔 “재할당+복사가 싫으면 연결리스트를 쓰면 되지 않나?” 싶었다. 하지만 이 방법은 더 나쁘다.

이 최적화의 목적 자체가 할당 횟수를 줄여 GC 부담을 낮추기다. 그런데 연결리스트는:

  • 원소 하나당 노드 객체를 매번 힙에 새로 할당 → 할당 횟수가 오히려 폭증

즉 “할당 줄이기”라는 목표를 정면으로 반대한다. 그래서 정답은 배열을 쓰되 초기 capacity를 지정해 재할당 자체를 없애는 것.


세 최적화의 공통된 뿌리

개선 사항의 원인이 사실상 하나다.

무분별한 객체 생성 → 메모리·GC 부담


장애 격리: 서킷브레이커(Circuit Breaker) 패턴

처음 가졌던 “왜 하나 죽으면 같이 죽지?”의 해답이 바로 이 패턴이다.

Spring에서 적용한다면?

처음엔 “빈이 상태를 들고 있다가 실패하면 상태를 바꾸고, 호출 시 그 상태를 확인하는 방식일까?”라고 생각했다. 방향은 비슷하지만 실제 표준 구현은 더 깔끔하다.

찾아본 방법

Spring Cloud Circuit Breaker 기준:

  • @CircuitBreaker 애너테이션을 메서드에 붙이면 AOP 프록시가 호출을 감싸서 슬라이딩 윈도우로 실패율을 집계하고, 임계 초과 시 호출을 차단한다.
  • 즉 “빈 자신이 상태를 들고 호출 측이 확인”하는 게 아니라, 빈을 감싼 프록시/데코레이터가 가로채는 형태다.
1
2
3
4
5
6
7
8
9
10
@CircuitBreaker(name = "userProfile", fallbackMethod = "fallback")
public List<Feed> getProfileFeeds(Long userId) {
  /*
    ...
   */
}

public List<Feed> fallback(Long userId, Throwable t) {
    return latestFeeds(userId); // 추천 대신 최신순
}

모니터링을 해본적이 있나?

나는 부끄럽지만, 아직까지 모니터링을 제대로 해본 적이 없었다.

그래서 아티클을 읽을 때 이 부분이 가장 공감이 안갔다.

이 부분에 대해서는 한 달 뒤의 나에게 넘겨야 겠다.


한 달 뒤의 내가 보면?

100% 이해했다고는 못 하겠지만, 다른 서비스에 요청할 때의 주의사항과 동기적으로 영향이 갈 수밖에 없는 구조를 이해한 것이 가장 큰 수확이었다.

또한, 한 달 뒤의 내가 이 아티클과 정리 내용을 본다면 어떤 생각이 들지 너무 궁금했다.

This post is licensed under CC BY 4.0 by the author.