본문 바로가기
👾 Server/👻 장애

주니어 백엔드 개발자가 서비스/서버 운영하며 배운 몇 가지

by kukim 2024. 3. 2.

이번 글에서는 서비스를 운영하며 겪었던 몇 가지 경험을 공유하려 합니다.

해당 글은 개인적인 경험과 책 <Release의 모든 것>을 참고하여 작성했습니다.


 

자기소개

문제는 피할 수 없다: 서비스 운영의 현실

Case 1. 타임아웃(timeout)

Case 2. 빠른 실패

Case 3. 서킷 브레이커 (circuit breaker)

Case 4. 데이터 정리하기

Case 5. 로드밸런싱과 스케일 아웃

Case 6. 서비스 재시작하기

Case 7. 인프라 자동화 도구를 조심하자 (조속기를 활용하자)

마치며: 처음 운영했던 때처럼 


자기소개

안녕하세요. 저는 특정 도메인 커머스 플랫폼에서 회사에서 추천 서비스, AB 테스트, 어드민 관리 등을 담당하는 개발자입니다. 업무 특징상 앱 실행 초기 팝업부터 메인 페이지 추천, 상품 상세 페이지에 이르기까지 광범위한 대용량 트래픽을 처리하고 있습니다. 특히 블랙프라이데이, 선착순 특가, 한정판 상품, 럭키 드로우와 같은 이벤트 시에는 트래픽급증하는 경험을 하고 있습니다.

문제는 피할 수 없다: 서비스 운영의 현실

소프트웨어, 서비스를 운영할 수 있다는 것은 감사한 일입니다. 유저와 트래픽이 있다는 것은 소프트웨어를 통해 클라이언트에게 가치를 전달할 수 있다는 것이기 때문이죠. 서비스 개발과 테스트, 계획까지 이 모든 것은 어찌보면 결국 '운영'을 해야 합니다. 초기 서비스 출시부터 시스템의 성장에 따라 운영 방법은 다양해지게 됩니다.

 

서비스 운영을 하면서 깨달은 가장 큰 진실 중 하나는, 장애는 피할 수 없다는 것입니다.

그림왕양치기의 '약치기 그림' 중

 

책 <릴리즈의 모든 것> 책에서 다음과 같이 말합니다.

안 좋은 일이 생긴다는 사실을 받아들여야 한다. ... (중략)
대신 할 수 있는 만큼의 조치를 취하고 예방하면서, 정말 심각하고 예상치 못한 피해가 발생하더라도 전체 시스템이 복구될 수 있게 만들어야 한다. - p35 중

 

네, 그렇습니다. 안 좋은 일은 반드시 일어납니다. 

받아들여야 합니다. ☠️

 

장애를 100% 막을 수는 없지만, 사전에 조치를 취하고 예방하여 피해를 최소화해야 합니다. (새벽에 잠을 자고 싶다면 말이죠)

 

서비스/서버를 운영하며 겪었던 몇 가지 경험을 소개하려 합니다.

 

Case 1. 타임아웃(timeout)

  • 목적: 서비스 응답 시간을 제한하여 불필요한 대기 방지
  • 배운 점: 적절한 타임아웃 값을 설정함으로써 사용자 경험 향상 및 서버 자원의 효율적 사용

시스템 운영에서 장애는 종종 미세한 균열에서 시작되어 전체 시스템의 붕괴로 이어질 수 있습니다. 특히 분산 시스템에서는 이러한 균열이 연계 장애를 유발하며, 한 계층의 문제가 다른 계층으로 확산되는 현상을 보게 됩니다. 예를 들어, 데이터베이스에 문제가 발생하면, 이를 호출하는 애플리케이션 모두에 영향을 미치게 됩니다. 이는 장애가 하나의 계층에서 다른 계층으로 전이되어 전체 시스템에 영향을 미치는 연쇄 반응을 일으킵니다.

 

연계 장애는 종종 하위 계층의 장애로 인해 자원 풀이 소진되면서 발생합니다. 이런 상황에서 시간제한(타임아웃) 설정이 없다면, 연계 장애는 거의 확실하게 발생합니다. 타임아웃 설정은 시스템이 응답을 기다리는 시간을 제한하여, 불필요한 대기 시간을 방지하고 장애의 확산을 막는 역할을 합니다.

 

타임아웃은 외부 API, DB 쿼리, 애플리케이션 특정 로직, 소켓, ... 등에 걸 수 있습니다.

빠르게 오류를 반환해서 시스템 부하를 감소시킬 수 있고 빠르게 오류를 탐지하여 시스템 안정성에 기여할 수 있습니다.

Case 2. 빠른 실패

  • 목적: 문제 발생 시 신속한 오류 반환으로 시스템 부하 감소
  • 배운 점: 오류 탐지와 대응 속도를 높여 시스템 안정성 유지에 기여

느린 응답이 응답 없는 것보다 더 나쁩니다. 하지만 최악은 응답이 느리게 왔는데 그 응답이 실패인 경우입니다.

쓸모없는 응답(실패)을 기다리느라 호출하는 쪽이나 응답하는 쪽 모두 CPU/네크워크 자원을 낭비하게 되는 것이죠.

 

빠르게 실패하는 것이 좋습니다.

호출하는 쪽에서는 타임 아웃을 걸어 특정 시간 넘도록 기다리지 않도록 할 수 있습니다.

응답하는 쪽이 서버인 경우 유효성 검사를 엄격하게 합니다. 호출하는 쪽의 이상한 입력이 비즈니스 로직, DB를 타지 않도록 사전에 방지하면 큰 효과를 가질 수 있습니다.

Case 3. 서킷 브레이커 (circuit breaker)

  • 목적: 장애 발생 시 트래픽 자동 차단으로 시스템 보호
  • 배운 점: 장애 확산 방지 및 빠른 복구 시간 도모

Circuit breaker - wiki image

서킷 브레이커는 전기 서킷 브레이커(회로 차단기)에서 이름을 가져왔습니다. 과부하 상태에서 시스템을 보호하기 위해 전기 회로에서 전류 흐름을 자동으로 차단하는 장치죠. 소프트웨어 개발에서 서킷 브레이커는 시스템의 특정 부분에 문제가 발생했을 때, 이를 자동으로 감지하고 호출을 중지하여 전체 시스템에 대한 장애 확산을 방지하는 역할을 합니다.

 

서킷브레이커를 활용해서, 내가 운영하는 서비스에 문제가 있다고 판단되면 서킷브레이커를 열어서, 호출하는 쪽에 문제가 있다고 알리고 호출을 멈추게 합니다.

 

여기서 문제 정의가 중요합니다. 어떤 문제가 있을 때 서킷을 열어야 할까요?

'최근 n분의 요청 중 50% 이상 실패하면 서킷을 오픈한다.' 와 같이 서비스에 맞는 문제 정의해야 합니다.

 

물론 서킷브레이커가 만능은 아닙니다.

사용하게 되면 시스템 복잡성이 증가하고, 상황에 맞는 상태 관리와 타임아웃 설정, fallback 로직, 재시도 로직 등 추가 코드 작업이 필요하게 됩니다. 또한 오류율 기반으로 서킷 브레이커를 연다고 한다면 적절한 설정값을 정하는 것이 어렵습니다. 너무 낮게 설정하면 너무 자주 열리고 문제가 될 수 있고 너무 높다면 장애 상황에 대응을 할 수 없습니다. 또한 오류율은 다양하게 영향을 받을 수 있습니다. 

 

Case 4. 데이터 정리하기

  • 목적: 저장 공간과 메모리 관리를 통한 서비스 장애 예방
  • 배운 점: 주기적인 로그 정리와 적절한 메모리 관리가 시스템 안정성에 필수적임

로그 파일을 계속해서 쌓아둔다고 합시다. 언젠가 서비스가 실행 중인 컴퓨터에 저장 공간이 가득 차게 되어 입출력 오류가 발생하여 결국 문제가 발생하게 됩니다.

 

Kubernetes(k8s) 환경에서 서비스 운영 중인 경우, nginx 컨테이너의 로그를 자동 지우는 logrotate 가 꺼져있었습니다. 결국 가득 차게 되어 장애가 발생한 일이 있었습니다. nginx 뿐만 아니라 spring 서버인 경우도 로그를 항상 저장하지 않고 일정 기간만 가지고 있어야 합니다. 원천 로그 데이터는 e.g. logstash를 활용해 다른 곳으로 보내는 것은 선택 아닌 필수 사항입니다.

 

메모리를 활용한 캐시도 마찬가지 입니다. 예를 들어 spirng 서버에서 ehcache를 사용한다고 가정합시다. ehcache cache에 만료시간을 걸지 않고 캐시 개수로 제한했다고 가정해 봅시다. 실수로 캐시 제한이 상당히 크다고 한다면, 메모리가 넘치게 되어 결국 OOM이 발생하게 됩니다. Redis 서비스에서도 마찬가지겠지만요.

Case 5. 로드밸런싱과 스케일 아웃

  • 목적: 트래픽 균등 분산으로 서버 부하 관리
  • 배운 점: 시스템의 확장성 및 부하 분산 능력 향상

로드밸런싱과 스케일 아웃은 트래픽 관리와 서버 부하 분산을 위해 널리 사용되는 전략이죠. 이미 많은 서비스들이 사용하고 있습니다.

서버가 8대가 있다면 서버 1대 당 전체 트래픽의 12.5% (100/8)를 처리합니다.

릴리즈의 모든 것 4.2장 그림 중

위 방법의 문제가 있습니다.

만약 한 서버가 장애가 발생하여 작동하지 않고자 죽는다면, 나머지 서버들이 해당 서버의 트래픽을 분담해야 합니다. 8대의 서버가 각각 12.5%의 트래픽을 처리하다가 하나가 실패하면, 나머지 7대는 각각 14.3%의 트래픽을 처리해야 합니다. 이는 각 서버 입장에서 부하가 약 15% 증가하는 것을 의미하며, 서버의 성능과 안정성에 부담을 줄 수 있습니다.

릴리즈의 모든 것 4.2장 그림 중

 

실제로 위와 같은 상황에서 장애를 겪은 경험이 있습니다. k8s 환경에서 keda를 활용하여 트래픽이 적은 특정 새벽 시간에 서버 수를 줄이도록 설정했습니다. 이때 남아있던 서버들이 트래픽을 갑자기 더 많이 받게되어 부하가 발생하여 장애가 발생하게 되었죠.

이 장애는 몇 번 반복되었습니다. (새벽 단잠을 깨우는 녀석 중 하나였습니다.)

Case 6. 서비스 재시작하기

  • 목적: 간단하면서 효과적인 문제 해결 방법
  • 배운 점: 정기적 재시작을 통한 시스템 안정성 유지의 중요성 인식

서비스 재시작은 소프트웨어 개발과 시스템 운영에서 오랜 시간 동안 사용되어 온 문제 해결 방법 중 하나입니다. 복잡한 시스템에서 발생하는 다양한 문제를 해결하는 데 있어 간단하면서도 효과적인 방법으로 인식되고 있습니다. 특히, 마이크로서비스 아키텍처(MSA)나 컨테이너화된 환경에서는 재시작 방식이 더욱 중요한 역할을 합니다. 

 

인스턴스 또는 컨테이너에 문제가 발생한 경우, 가능한 한 빨리 재시작하는 것이 좋습니다. k8s와 같은 오케스트레이션 도구는 컨테이너의 상태를 모니터링하고, 문제가 감지되면 자동으로 컨테이너를 재시작하는 기능을 제공합니다. 이러한 자동 재시작 메커니즘은 시스템의 빠른 회복을 가능하게 하며, 사용자 경험의 중단을 최소화합니다.

 

이는 언제나 k8s 환경의 컨테이너는 재시작이 될 수도 있다는 것을 의미합니다. 단일 pod로 사용한 경우, 서비스가 갑자기 죽을 수도 있습니다. 예를 들어 AWS 스팟 인스턴스를 사용한 경우 갑자기 클러스터 내 인스턴스가 내려갈 수 있습니다. 또한 k8s 환경에서 pod는 언제나 재시작될 수 있다는 것을 인지하고 있어야 합니다.

Case 7. 인프라 자동화 도구를 조심하자 (조속기를 활용하자)

구글 검색 - 조속기

조속기(governor)는 자동화된 시스템 변경 과정에서 조작의 속도를 의도적으로 늦추어, 자동화가 너무 빠르게 진행되어 발생할 수 있는 문제를 예방하는 메커니즘입니다. 이는 자동화 프로세스에 '사고할 시간'을 부여하여, 필요한 경우 인간의 개입을 가능하게 하는 안전장치 역할을 합니다.

 

자동화는 반복적이고 규모가 큰 작업을 효율적으로 처리할 수 있는 능력을 제공하지만,레딧 장애 사례와 같이 예기치 않은 상황에서는 적절한 판단을 내리지 못할 수 있습니다. 이는 자동화에는 본질적으로 인간의 판단력이 결여되어 있기 때문입니다. 그러나 모든 작업을 수동으로 처리하는 것은 시간과 자원이 많이 드는 일이므로, 자동화된 변경 관리 과정에서 조속기를 사용하는 것이 필요합니다.

 

레딧의 장애 사례가 있습니다.(https://www.reddit.com/r/announcements/comments/4y0m56/why_reddit_was_down_on_aug_11/)

  • 주키퍼 클러스터 업그레이드할 수 있도록 오토스케일링 서비스 종료
  • 업그레이드 과정 중 패키지 관리 시스템이 오토스케일링 서비스 꺼져있음을 감지하고 재실행
  • 오토스케일링 서비스 다시 작동하면서 일부만 이전된 주키퍼 데이터 읽음
  • 전환 중이던 주키퍼 데이터는 기존에 실행 중인 것보다 훨씬 작은 규모의 환경 반영
  • 오토스케일링 서비스는 너무 많은 서버가 실행되고 판단해 다수 애플리케이션 및 캐시 서버 종료
  • ⇒ 장애 시작

 

자동화되는 과정에서 사람이 개입할 수 있도록 속도를 늦출 수 있습니다. 예를 들어 무중단 배포에서 롤링 업데이트 비율을 낮추거나 카나리 배포를 할 수 있습니다. AWS EC2 인스턴스의 자동화가 1000대까지는 안정적이라고 판단된다면, 2000대까지 증가하는 경우에는 증설을 천천히 진행하도록 설정합니다. 이를 통해 자동화 프로세스의 속도가 너무 빠르게 진행되어 발생할 수 있는 위험을 줄일 수 있습니다. (오토스케일링 무제한 켜놓는다면 과금 폭탄이..)

 

자동화에는 감당 가능한 / 사람이 컨트롤 가능한 limit을 걸고 제한에 가까워진다면 알림/모니터링을 하여 사람이 개입할 수 있도록 하는 것이 중요합니다.


마치며: 처음 운영했던 때처럼 

서비스/서버를 운영하며 겪었던 몇 가지 경험을 책과 함께 정리했습니다. 책 Release의 모든 것 덕분에 업무에서 배웠던 장애를 되돌아 볼 수 있어서 더 좋은 시간이었습니다. 작동하지 않는 코드를 운영을 통해 성장할 수 있어 좋은 경험을 하고 있습니다. 때로는 스트레스도 많이 받고 있지만요. 책 옮긴이(박성철님)의 말처럼 현실에서 잘 작동하는 프로그램을 잘 만들고 싶습니다. 운영 고려 설계(design for production)를 하여 단순히 기능 정상 작동을 넘어 운영 상황에서 만날 다양한 문제를 고려해 SW 설계에 반영하고 싶습니다.

감사합니다.

 

처음 만날때 처럼 - 윤종신

('윤종신의 처음 만날 때처럼'을 개사했습니다.)

 

처음 운영했던 때처럼 

졸린 눈을 비비며 슬랙 메시지를 읽었어
마냥 즐거웠던 내 마음 한순간 무너졌어
500에러란 글자 위에 떨어진 한숨을 내쉬었어
마냥 웃음짓던 내 얼굴은 한순간 어두워졌어

 

왜 그리 갑자기 타임아웃 났다고 했어
왜 그리 쉽게 서킷 열린다고 했어
제발 꿈이었으면 그냥 개발 서버였으면 좋겠어
어제 배포했던 그 코드가 문제야

테스트는 그리 쉬운 게 아냐
우리가 처음 기능 구현할 때처럼 말야

 

왜 그리 갑자기 서버가 죽었다고 했어
왜 그리 쉽게 디비가 터졌다고 했어
제발 꿈이었으면 그냥 모니터링 실수였으면 좋겠어
이제까지의 모든 업타임이 아쉬워

 

안녕은 그리 쉬운 게 아냐
우리가 처음 서비스 운영할 때처럼 말야

 

 

 

 

댓글