29LIVE 개발기

Junghyungile
29CM TEAM
Published in
16 min readMay 22, 2023

소개

안녕하세요, 29CM에서 첫 커리어를 시작한 커머스백엔드 개발자 정형일입니다. 저는 며칠 후면 29CM에 입사한지 딱 1년이 되는데요, 지난 1년을 돌아보면 이것저것 한 일들이 꽤 많은 것 같습니다.

오늘은 그 중 대표적으로 기억에 남는 “29LIVE 개발기”를 한 번 공유해보려고 하는데요, 부족하지만 재미있게 읽어주시면 감사하겠습니다.

29CM, 브랜드 토크쇼 형식 라이브 커머스 ‘29LIVE’ 론칭

29LIVE는 취향별 큐레이션과 스토리에 초점을 맞춘 라이브 방송입니다. 상품을 소개하고 판매하는 것에 그치지 않고, 방송을 통해 브랜드 스토리와 새로운 라이프스타일을 깊이 있게 소개하는 것이 특징입니다. 2022년 9월 첫 방송을 시작으로 매주 고객에게는 브랜드의 숨겨진 가치를 전하고 브랜드에는 고객과의 새로운 소통의 장을 제공하기 위해 콘텐츠를 지속 발전시켜가고 있습니다.

빠른 실험을 통해 29LIVE의 비즈니스 가치 증명

저는 특정한 상황에서는 빠른 개발을 통한 빠른 실험이 여러 비용을 절감할 수 있는 기회를 제공한다고 생각합니다. 작은 규모에서 빠르게 실험을 시도하고 그 결과를 분석하는건 실패할 가능성이 큰 아이디어나 전략을 사전에 빠르게 확인할 수 있다고 생각하고 있거든요. 또한 이를 통해 비효율적인 아이디어에 많은 자원을 투자하는 것을 피하고 효과적인 전략에 집중할 수 있는 장점도 있다고 생각합니다.

29LIVE 스트리밍 서버는 29CM에서 자체적으로 개발한 서비스가 아닌, 외부 업체 솔루션을 연동하여 빠른 개발 및 실험을 진행했습니다. 만약 29CM만의 독자적인 기능 욕심을 위해 자체적으로 라이브 스트리밍 서버를 구축했다면 29LIVE를 론칭하기 까지 꽤나 오랜 시간이 걸렸을 것입니다. 저희는 외부 업체 솔루션을 연동하여 개발을 진행함으로써 약 1주 정도의 개발 시간만 소요하여 빠르게 첫 방송을 론칭할 수 있었습니다.

그리고 이러한 빠른 실험을 통해, 29LIVE가 비즈니스 가치가 있다는걸 빠르게 증명할 수 있게 되었습니다.

  • 평균 시청 지속시간은 최대 10분 이상으로 타 콘텐츠 대비 높은 고객 참여도
  • 방송 중, 상품 프로모션을 진행한 결과 높은 구매 전환율을 보이며 라이브 1시간 내에 일평균 매출을 달성하는 성과
  • 방송 전후를 비교했을 때 고객의 브랜드 관심 행동 지표가 최대 170% 이상 상승

비록 29LIVE가 비즈니스 가치가 없다는 것이 증명되었더라도, 해당 실험에 저의 큰 리소스를 낭비하게 되는 일은 발생하지 않았을 것입니다. 오히려, 적은 리소스를 통해 빠르게 실험을 시도하고 그 결과를 분석하여 실패할 가능성이 큰 아이디어나 전략을 사전에 빠르게 확인할 수 있는 의미있는 개발이었다고 생각해요.

만약 자체 라이브 스트리밍 서버를 구축하여 개발을 진행했다면 저에게는 이전에 경험해본 적 없던 새로운 기술들을 경험해볼 수 있는 기회였을 것입니다. 하지만 저는 꼭 새로운 기술만 추구하는 것은 좋은 선택지가 아니라고 생각합니다. 좋은 선택지란, 새로운 기술만 추구하기 보다는 명확한 목적과 제약 조건을 고려하여 기술과 개발 방향을 선택하는 것이 아닐까요?

따라서, 아직 비즈니스 가치를 예측할 수 없었던 29LIVE 같은 경우 외부 연동 솔루션을 도입해 빠른 개발을 통한 빠른 실험을 진행했던건 효율적이면서도 효과적인 선택지였다고 생각합니다. 빠르게 29LIVE의 비즈니스 가치 유무를 파악해 볼 수 있었거든요ㅎㅎ

그럼 이제 빠르게 개발을 진행했던 29LIVE의 아키텍쳐를 간단히 소개 드려보도록 하겠습니다.

29LIVE 아키텍쳐

1. STAFF가 29LIVE admin service에 방송 정보를 송출합니다.

2. client에서 content service 재/생방송 정보를 요청합니다.

  • 모든 라이브 방송은 각각 campaing key라는 고유한 키를 가지고 있습니다.
  • 클라이언트에서 서버의 API 호출시 특정 방송의 campaing key만 인자로 넘겨주면 특정 방송에 관한 응답만 받을 수 있습니다.

3. content service에서 방송 정보 조회를 위해 29LIVE admin service에 api call을 요청합니다.

4. 29LIVE admin service에서 에러 혹은 응답 지연시 정해진 횟수만큼의 retry를 시도하고 정해진 횟수를 초과한다면 클라이언트에게 fallback을 응답합니다.

5. 29LIVE admin service에서 정상 응답이 조회된다면 조회된 방송 정보를 redis에 캐싱합니다.

  • 29LIVE는 트레픽이 높은 편이라 부하를 줄이기 위해 방송 정보를 redis에 캐싱합니다.
  • 평소 TTL(Time to Live)은 5분으로 유지합니다.
  • 29LIVE는 20시 ~ 21시에 생방송을 진행합니다. 따라서 spring scheduler를 통해 생방송 시간에 맞춰 캐시 무효화를 진행합니다.

6. 생방송 혹은 재방송 정보를 클라이언트에게 응답합니다.

제가 위 아키텍쳐를 설계하며 우려했던 점은 아래 두 가지 입니다.

1. 생방송 시작 전에 빈 응답이 캐싱되어 생방송때도 빈 응답을 내려주는 경우

생방송 시작 전 해당 방송의 campaignKey로 API를 호출할 경우 방송이 시작되지 않아 빈 값을 내려줍니다. 만약 특정 유저가 19시 59분에 API를 호출했는데 우연히 빈 값이 캐싱된다면, TTL이 5분이라 20시 04분까지 캐싱이 유지됩니다. 캐싱이 유지될 경우 20시에 생방송이 시작되어도 20시 04분까지는 빈 값을 내려주기 때문에 방송에 문제가 발생할 우려가 있습니다.

만약 방송에 문제가 생길 경우, 고객들에게 정말 좋지 않은 가치를 전달하게 되고 이로인해 29CM 비즈니스에도 큰 손실이 발생하게 됩니다.

따라서, 이러한 우려를 사전에 방지하기 위해 Spring Scheduler를 사용해 아래와 같이 생방송 시작 시간에 맞춰 캐시 무효화가 진행될 수 있도록 해주었습니다.

@GetMapping
@Cacheable(cacheNames = CacheKey.LIVE_STREAMING_CACHE_KEY_V1, key = "#campaignKey")
public CommonResponse<LiveStreaming> getLiveStreaming(
@RequestParam String campaignKey
) {
..생략
return CommonResponse.success(response);
}

@Scheduled(cron = "0 0 20 ? * MON-FRI")
@CacheEvict(cacheNames = CacheKey.LIVE_STREAMING_CACHE_KEY_V1, allEntries = true)
public void clearCacheEveryMinute() {}

하루에 한 번 캐시 무효화를 진행하는 위의 작업은 다소 간단한 작업이라서, 다른 프레임워크나 라이브러리 의존성을 추가하지 않고도 가장 간단하게 사용할 수 있는 Spring Scheduler를 사용하였습니다.

2. content service에서 방송 정보 조회를 위해 29LIVE admin service에 api call 요청시, 29LIVE admin service에서 장애 혹은 응답 지연이 발생한 경우

29LIVE admin service API 호출이 지연되면 해당 서비스의 전체적인 응답 시간도 느려질 수 있습니다. 만약 만약 응답 대기 시간이 무한정 지속되면, 전체적인 서비스에 영향을 끼치게 됩니다. 29CM는 마이크로서비스 기반의 아키텍쳐로 운영되고 있기 때문에, 만약 특정 서비스에 장애 발생시, 다른 마이크로 서비스까지 장애가 번질 우려도 있습니다.

이를 해결하기 Resilience4j2의 circuit breaker를 이용해 29LIVE admin service에 장애 혹은 지연이 발생할경우 해당 서비스의 호출을 차단하여 전체적인 서비스 장애로 전파되는 것을 방지하고자 하였습니다.

서킷 브레이커(Circuit Breaker)란? 분산 시스템에서 서비스 간의 호출을 보호하기 위한 디자인 패턴입니다. 가장 간단하게 설명하면, 서킷 브레이커는 호출된 서비스의 상태를 모니터링하고, 일정한 실패 비율이나 지연 시간을 초과하는 호출이 발생할 경우 해당 서비스 호출을 차단하여 장애가 전파되는 것을 방지합니다.

content service에서 29LIVE admin service API 호출시, 29LIVE admin service의 장애 혹은 지연이 발생하면

  • 우선 정해진 횟수 만큼 다시 조회를 재시도(retry)를 합니다.
  • 정해진 횟수 만큼 조회를 재시도 했지만, 모두 실패한다면 연결을 끊고 fallBack을 응답합니다.
  • 그 외 추가로 설정한 옵션들은 아래에 작성해두도록 하겠습니다.

아래는 제가 application.yml에 추가한 circuitbreaker 관련 환경 변수들 입니다. 자신이 운영하고 있는 환경을 고려하여 적절하게 설정해주는 것이 가장 좋은 방법입니다.

resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: <슬라이딩 윈도우 크기>
minimum-number-of-calls: <최소 호출 횟수>
record-exceptions:
- java.lang.Throwable
permittedNumberOfCallsInHalfOpenState: <반 열린 상태에서 허용되는 호출 횟수>
waitDurationInOpenState: <오픈 상태에서 대기 시간>
failureRateThreshold: <실패율 임계값>
eventConsumerBufferSize: <이벤트 소비자 버퍼 크기>
registerHealthIndicator: <헬스 인디케이터 등록 여부>
sliding-window-type: time_based
instances:
external-api-circuit-breaker:
base-config: <기본 구성>
slow-call-duration-threshold: <느린 호출 지연 시간 임계값>
failure-rate-threshold: <실패율 임계값>

아래는 FeignClient를 이용해 29LIVE ADMIN SERIVCE를 호출할때 위해서 설정한 서킷브레이커 패턴을 적용했던 간단한예시 코드입니다.

@FeignClient(name = "LiveStreamingClient", url = "29LIVE ADMIN SEIVCE")
public interface LiveStreamingApiCaller {

@GetMapping(value = "생략")
@CircuitBreaker(name = "external-api-circuit-breaker", fallbackMethod = "fallback")
LiveStreamingApiResponseDTO.LiveStreaming getLiveStreaming(
..생략
);

// Fall Back 메서드 호출
default LiveStreamingApiResponseDTO.LiveStreaming fallback(Exception e) {
log.error("LiveStreaming Api Call Error : {}", e.getMessage())
return LiveStreamingApiResponseDTO.LiveStreaming.empty();
}
}

위의 코드처럼 저는 circuit breaker 패턴을 사용해 29LIVE ADMIN SERVICE에 장애 혹은 지연이 발생해도 전체적인 서비스로 장애로 전파되는 문제를 사전에 방지하였습니다.

추가로, 가장 빠르고 심플한 방법으로 circuit breaker 패턴을 도입하기 위해 선택할 수 있는 대안은 Resilience4j2와 Hystrix가 있었습니다. 그 중 Resilience4j2를 선택한 이유는 아래와 같습니다.

  • Resilience4j2는 현재까지도 개발이 활발하게 이루어지고 있으며, 커뮤니티의 지원이 잘 이루어지고 있습니다. Hystrix는 Netflix에서 개발되었으며, 2018년에 유지보수 지원 중단을 선언하였습니다. 따라서 Resilience4j2는 최신 버전을 사용하여 유지보수와 커뮤니티 지원을 받는 것이 좋다고 생각했습니다.
  • Resilience4j2는 회로 차단, 재시도, 타임아웃, 실패 회복, 쓰레드 풀, 백프레셔 등 다양한 모듈을 제공합니다. 개발자는 이 중 필요한 기능을 선택하여 사용할 수 있고, 이러한 모듈화된 구조는 기능의 조합과 변경을 쉽게 할 수 있도록 합니다. 그에 비해 Hystrix는 회로 차단 기능에 더 중점을 두고 있으며 다른 기능은 상대적으로 제한적입니다. 또한, Hystrix는 단일 모듈로 구성되어 있어서 모듈 간의 선택적인 사용이 어렵습니다.

그 외 resilience4j에 관한 더욱 자세한 정보는 공식 문서를 참고해주세요.

개발은 끝났고.. 이제 모니터링?

언제 한 번 라이브 방송중에 장애가 발생한 적이 있었습니다. 장애가 발생한 원인에 관해 간략히 말씀드리면, 저희는 라이브 방송을 진행할 때 관련된 서비스의 파드를 평소보다 조금 더 늘려 놓습니다. 라이브 방송때는 평소보다 트레픽이 급격히 증가하기 해당 서비스의 메모리 사용률이 급격하게 올라가기 때문이죠. 하지만, 해당 라이브 방송때는 이상하게 초기 방송 시청 예약 수가 평소보다 조금 더 적었습니다. 그래서 저는 오늘은 굳이 파드를 늘릴 필요가 없을 것이라고 생각을 했고, 다른 분들께도 늘릴 필요가 없을 것 같다고 제안을 드렸습니다. 따라서 해당 방송때는 굳이 늘리지 않고 적은 수로 운영을 하였습니다.

그러나 방송이 시작되고 나서 제 생각과는 다르게 급격히 트레픽이 증가하였고, 결국 Heap의 메모리가 꽉차 OutOfMemory Error 발생하여 서비스가 다운되어 버렸습니다.

장애가 발생해 조금 놀랐지만, 다행히도 혹시 모를 상황에 대비하여 미리 모니터링을 진행하고 있었기 때문에 OutOfMemory Error 발생하여 파드들이 다운된 걸 바로 캐치할 수 있었습니다. 따라서, 바로 이에 관해 슬랙 채널에 공지하고 동료들과 바로 대응에 나섰습니다.

우선 고객 가치가 가장 중요하기 때문에 클러스터의 노드 추가 및 cpu, memory 증설등의 작업을 바로 진행하여 복구 작업을 진행하였습니다. 그리고 9분 후에 바로 다시 방송을 재개할 수 있었습니다. 저를 포함한 동료분들이 고객 가치를 위해 미리 라이브 방송 전부터 모니터링을 진행해주고 계셨기 때문에 재빠르게 대응할 수 있었던 것 같습니다.

그리고 해당 장애 발생 이후 아래와 같은 성능 개선 작업과 주기적인 모니터링을 진행하며 불필요하게 발생하는 메모리 사용률을 점진적으로 낮추었습니다.

  • 해당 서비스의 주요 API는 전부 캐싱
  • 메모리릭이 발생하거나 불필요하게 과다로 생성되는 객체들을 찾아서 개선
  • 하이버네이트의 옵션 중 하나인 in_clause_parameter_padding를 true로 설정하여 여러 In절 쿼리에서 IN절 Query Plan Cache를 재사용
  • redis 점검
  • cpu, memory 증설
IS-AS
request/limit
cpu /
memory Mi / Gi

TO-BE
request/limit
cpu /
memory Gi / Gi
  • 등등

결국 메모리 사용률이 아래와 같이 점진적으로 개선되는 것을 확인할 수 있었습니다.

이렇게 지속적인 성능 개선 작업과 모니터링을 진행하며 불필요하게 발생하는 메모리 사용률을 점진적으로 낮춰보는 일은 저에게도 좋은 경험이 되었습니다.

장애없고 좋은 성능의 서비스를 고객에게 전달하기 위해서는 모니터링을 자주하는 것이 참 중요하다고 생각합니다. 아래는 연예인 김종국님이 하신 명언 중 하나입니다.

전 개발도 똑같다고 생각합니다.

“개발은 배포 후 모니터링 하는 것까지가 개발이다”

성장하는 서비스에서 장애는 언제든지 발생할 수 있다고 생각합니다. 사실 장애 보다는 내가 배포한 기능을 실제 운영하며 애플리케이션의 CPU, 메모리, 커넥션 사용, 고객 요청수 등과 같은 수 많은 지표들을 모니터링하며 점진적으로 서비스를 개선해나가는 과정이 정말 중요하다고 생각합니다.

매 29LIVE 방송마다, 저희는 항시 모니터링을 진행하면서 문제를 찾아 개선하며, 다음 라이브 방송을 준비하고 있습니다. 이러한 과정들은 고객에게 더 나은 비즈니스 가치를 제공해줄 뿐만 아니라, 제 개인적인 성장에도 도움이 참 많이 되는 것 같다고 느끼고 있습니다.

29LIVE를 개발하며 느낀점

  1. 개발을 할때 단순히 특정 기술 혹은 멋지고 화려해 보이는 기술만 사용하려고 하는 자세는 좋은 개발자의 자세가 아니라고 생각합니다. 제일 중요한건 현재 상황에 맞게 명확한 목적과 제약 조건을 고려하여 기술과 개발 방향을 선택하는 것이 가장 좋은 개발자의 자세라고 느꼈습니다.
  2. 자신이 개발한 기능은 자신이 책임지고 모니터링까지 진행하는 것이 중요한 것임을 깨달았습니다. 해당 기능을 개발한 개발자가 직접 모니터링 하는 것이 빠른 이슈 파악과 대응, 개선과 최적화, 사용자 경험 개선, 효율적인 디버깅등의 면에 있어 가장 효과적이고 효율적이기 때문입니다.

맺음말

29CM에서 첫 커리어를 시작한지 벌써 1년이 다 되어 갑니다. 그동안 29CM의 좋은 동료들 덕에 정말 많이 성장한 것 같아요! 이따금 부족한 모습들도 많이 있었지만 늘 격려해주시고 좋은 가르침 주신 29CM 동료분들께 다시 한 번 감사의 인사 전달 드립니다. 2023년도 잘 부탁드립니다!

[함께 성장할 동료를 찾습니다]

29CM (무신사) 는 3년 연속 거래액 2배의 성장을 이루었습니다.

더 빠르고 큰 성장을 위하여 라이브 커머스를 비롯한 여러 콘텐츠들을 고도화 하고 고객에게 더 나은 가치를 제공하기 위해 여러 실험과 고객의 관점에서 깊은 분석을 시도하고 있습니다.

함께 성장하고 유저 가치를 만들어낼 동료 개발자분들을 찾습니다.
많은 지원 부탁드립니다!

🚀 29CM 채용 페이지 : https://www.29cmcareers.co.kr/

--

--