선제적 장애 대응을 위한 Sentry 최적화 적용기

1 week ago 8

들어가며

서비스가 성장했다는 걸 언제 체감하시나요? 보통 서버 트래픽, MAU(Monthly Active Users), 시장점유율, 영업이익 등을 지표로 삼지만, 프론트엔드 개발자로 일하면서 조금 다른 방식으로 그 순간을 경험했습니다.

어느 날, 고객센터를 통해 서비스 장애가 보고되었습니다. 서비스에 치명적인 문제가 발생했지만 내부적으로 이를 미리 감지하지 못했습니다. 서비스에 도입된 Sentry는, 정작 중요한 순간에 제대로 작동하지 않았습니다. 뒤늦게 확인한 결과, Sentry의 에러 수집량 제한으로 인해 수집된 에러 로그의 80%가 유실되고 있었습니다. 즉, 과도하게 많은 에러 로그가 발생하면서 Rate Limit에 도달했고, 이로 인해 정작 중요한 장애 관련 로그까지 누락되어 상황 파악이 늦어졌던 것입니다.

Sentry는 애플리케이션에서 발생하는 에러와 성능 지표를 실시간으로 추적하고 감지할 수 있게 도와주는 모니터링 도구입니다

서비스 초기에는 Sentry가 매우 잘 동작했을 것입니다. 하지만 시간이 지나면 변하기 마련일까요. 새로운 기능을 개발해야 했고, 일정은 마음을 빡빡하게 만들기도 합니다. 새로운 맥락들이 과거의 결정들과 함께 코드에 고스란히 남아서 복잡하게 얽힙니다. 리팩토링으로 기술 부채를 해결하기도 하지만 서비스가 성장하면서 생기는 모든 에러를 막기는 어려울 것입니다.

그럼에도 불구하고, 서비스가 빠르게 성장할수록 안정성과 신뢰성을 유지하는 일은 점점 더 중요해집니다. 서비스 규모가 커지면서 사소한 문제라도 수많은 사용자에게 영향을 주어 결코 사소하지 않은 파급력을 갖게 되기 때문입니다. 따라서 성장하는 서비스일수록 안정성과 신뢰성 확보는 결코 포기할 수 없는 핵심 요소입니다.

우리가 쓰던 Sentry 그릇

운영 서비스에 Sentry를 적용할 경우 대부분 유료 플랜을 사용하게 됩니다. Sentry는 각 플랜별로 제공하는 수집 가능한 에러 수가 다르며, 서비스 규모와 여건에 맞게 사용할 수 있습니다.

하지만 여기에는 중요한 제약 사항이 있습니다. Sentry의 월간 에러 수집량은 Organization 단위로 할당되며, 하위 프로젝트가 서로 수집 가능한 에러 수를 공유한다는 것입니다. 즉, 특정 프로젝트에서 대량의 에러가 발생하면 전체 한도를 독식할 가능성이 있습니다.

이 문제를 해결하기 위해 사내에서는 프로젝트별로 단위 시간당 수집 가능한 에러 수에 제한을 두는 정책을 운영하고 있습니다. 하지만 트래픽이 많은 서비스에는 다소 불리한 구조일 수밖에 없습니다. 제한을 초과할 경우, Sentry Stats에서 다음과 같이 그래프 상단이 잘린 듯한 차트를 맞이할 수 있습니다.

단위 시간당 수집되는 에러 수 차트

위와 같은 상황에서는 단순히 중요한 에러를 유실할 수 있다는 것 이외에도 치명적인 문제가 있습니다. 수집되는 에러 수가 항상 일정한 수준에서 유지되기 때문에, 장애 상황을 쉽게 구분할 수 없다는 점입니다.
실제로 내부에서 장애를 탐지하기 어려웠던 원인이죠.

저희의 목표는 내부에서 선제적으로 장애를 탐지하는 것입니다. 이를 위해 다음과 같은 개선 방향을 설정했습니다.

  1. 에러 수집 고도화: 수집되는 에러 수를 비약적으로 줄이면서도, 각 에러가 최대한 많은 정보를 담고 효과적으로 그룹화되도록 개선해야 합니다.
  2. Alerts 최적화: 장애를 빠르게 감지할 수 있도록 알람을 효과적으로 활용해야 합니다. 너무 잦은 알람은 무뎌지기 마련이므로, 불필요한 알람을 줄이는 것이 중요합니다.
  3. 안정적인 모니터링 프로세스 구축: 에러 알람에 무뎌지지 않도록, 장기적으로 유지 가능한 모니터링 프로세스를 마련해야 합니다.

Sentry 최적화로의 여정

지금까지는 문제를 발견하고 방향을 잡아가는 과정을 공유했습니다. 이제부터는 Sentry를 더 똑똑하게 활용하고, 체계적으로 관리하기 위해 시도한 구체적인 경험과 사례들을 이야기해 보겠습니다.

Sentry 로그 정보를 고도화(Enrich)하자

위 이미지를 보면 어떤 에러인지 감이 오시나요?

"Axios 인스턴스를 통해 요청한 API에서 400, 401 에러가 발생했다"라는 정보 이외에 별다른 정보를 한눈에 파악할 수는 없겠죠. 얼마나 심각한 에러인지, 어떠한 API인지, 어느 페이지에서 발생했는지는 전혀 알 수 없습니다.

에러가 발생했을 때는, 다음과 같은 정보를 한눈에 확인할 수 있어야 합니다.

  • 발생 장소: 어떤 API에서 에러가 발생했는가?
  • 세부 사항: 어떤 쿼리 파라미터가 사용되었고, 어떤 화면에서 요청이 이루어졌는가?
  • 발생 환경: 사용자 운영체제(OS), 앱 버전, 기종 등은 무엇인가?
  • 발생 시점: 특정 릴리즈 이후 발생한 문제인가?

이는 Sentry 라이브러리에서 제공하는 captureException 메서드로 단순히 JavaScript의 Error 객체를 전송하는 것을 넘어, Scope라는 추가적인 맥락 정보를 전달해 해결할 수 있습니다.

  • Level: 에러의 심각도 지정
  • Context: 에러의 맥락 정보를 저장
  • Tags: 검색과 분류를 용이하게 하는 태그
  • Fingerprint: 에러 그룹화를 위한 고유 식별자

위 기능을 활용하면 에러 발생 시 핵심 정보를 한눈에 파악하면서도, 문제 해결에 필요한 상세 맥락 정보까지 로그에 효과적으로 담아낼 수 있게 됩니다. 결과적으로 에러가 발생한 상황과 롤백 시점을 빠르게 파악할 수 있고, 필요시 협업이 필요한 유관부서도 한 번에 파악할 수 있겠죠. 그렇다면 저희가 에러 로그를 고도화해나간 과정을 하나씩 살펴보도록 하겠습니다.

1. 에러 Level을 정하자

Sentry에서 Level 필드는 쿼리 및 Alert 설정에서 필터링 조건으로 활용할 수 있기에 간단하지만 큰 효용 가치를 제공하는 정보입니다. 에러의 원인과 영향도는 다양하기 때문에, 서비스에 미치는 영향을 기준으로 에러 Level을 정의했습니다. 정의한 에러 Level을 기준으로 Critical Alert은 fatal level이 한 개라도 발생하는 경우, Warning Alert은 낮은 수의 에러가 몇 개 이상 발생하는 경우 등으로 나누어 설정하였습니다.

Level 설명
fatal
  • 화면을 정상적으로 렌더링하지 못하는 에러
  • 필수 기능이 정상적으로 동작하지 못하는 에러
error
  • 예상하지 못하고 핸들링되지 않은 모든 에러 (Default)
warning
  • 발생을 예상할 수 있고, 서비스에 영향을 주지 않는 수준의 에러 (예: API Timeout)

2. 에러 이름만으로 핵심 정보를 파악하도록 하자

다음으로는 Sentry의 이슈를 직관적으로 파악할 수 있도록, 에러의 이름(name)에 상태 코드와 API 경로를 포함하도록 개선했습니다.

HTTP Client인 Axios의 AxiosError를 확장한 코드를 예시로 보여드릴게요.

class SentryNetworkError extends AxiosError { private static generateName(error: AxiosError): string { const status = error.status const baseURL = error.config.baseURL const path = error.config.url.split('?')[0] const replacePathParams = path.replace(/\/\d+(?=\/|$)/g, '/{id}') return `[${status} Error] - ${baseURL}${replacePathParams}` } constructor(error: AxiosError) { this.name = SentryNetworkError.generateName(error) } }

위 코드를 적용한 결과는 다음과 같습니다.

  • Sentry 이슈 제목이 [500 Error] https://some-api-host.baemin.com/path/{id} 형태로 생성됩니다.
  • URL 경로 내 가변 값(PathParams)은 {id}로 치환하여 동일한 API 에러를 그룹화할 수 있습니다.

3. 상세한 정보도 담자

에러를 재현하지 않고도 상세한 정보를 확인할 수 있도록 추가적인 데이터를 Sentry의 Context와 Tags로 적재했습니다.

Context에는 API의 쿼리 파라미터, 에러 응답, 헤더 등의 요청 맥락을 파악하기 위한 정보들과, 유저의 운영체제, 앱 버전, 릴리즈 버전, 휴대폰 기종 등의 공통 맥락 정보를 포함했습니다.
Tags에는 실제로 에러가 발생한 지면 정보, 릴리즈와 같이 검색에 필요한 정보들을 담았습니다.

또한 Sentry에서 제공하는 그룹화 기능을 위해 커스터마이징한 Fingerprint를 구현했습니다. 기본적으로 Fingerprint는 StackTrace를 기준으로 만들어지기 때문에, 보다 정확하게 에러들을 그룹화하기 위해서는 Custom Fingerprint를 정의해야 편리합니다.

withScope((scope) => { // 상태 코드, 메소드, 요청 경로에 따른 Fingerprint 선언 scope.setFingerprint([status, method, endpoint]) // 1) API 요청, 응답 맥락 정보, 2) 유저 환경 정보 scope.setContext({ response: errorResponse, os: userOS }) // 에러 Level 정의 scope.setLevel('fatal') // 기타 검색을 위한 태그 설정 scope.setTag('Error Page', '지면 이름') // 에러 전송 captureException(customErrorObject) })

위 개선 사항들을 적용한 결과, 다음과 같은 효과를 얻을 수 있었습니다.

  • 실시간 에러 영향도 대응: 서비스에 치명적인 에러를 빠르게 식별하기 위해 에러 Level별로 Alert를 설정하고, 에러의 영향도를 세분화하여 장애 발생 시 즉각적으로 인지할 수 있도록 구성하였습니다.
  • 효율적인 원인 분석: 에러 상황을 재현하는 수고 없이도 Context와 Tags에 기록된 상세 정보(사용자 환경, API 요청/응답 데이터 등)를 통해 신속하게 원인을 분석할 수 있습니다.
  • 명확한 에러 분류 및 그룹화: 무분별하게 쌓이던 API 관련 에러들이 엔드포인트별로 명확히 분류되고, 동일 API 에러는 하나의 이슈로 묶여 관리되어 추적 및 통계 파악이 용이해졌습니다.

수집이 불필요한 로그들을 필터링하자

에러 로그를 세분화한 후 Sentry를 확인해보니 중요하지 않은 에러 로그들이 너무 많았습니다. 저희가 일상적으로 쓰는 메일함에 중요하지도 않은 스팸 메일이 너무 많이 쌓인다면 어떨까요? 새 메일 알림은 노이즈가 될 것이고, 중요한 메일은 노이즈에 묻혀 놓치게 될 수 있습니다. 저희의 Sentry도 스팸 메일이 가득한 메일함과 다를 바 없는 상태였습니다.

하지만 서비스에 실시간으로 발생하는 에러가 많다고 서비스에 치명적인 문제가 있는 것은 아닙니다. 이미 비즈니스 로직에서 핸들링되어 수집될 필요가 없는 에러, 단말기의 네트워크 문제로 인한 에러 등 서비스에 영향이 없는 에러 로그들이 사용량을 소모하고 있었습니다. 이를 해결하기 위해 불필요한 에러 로그를 선별하여 Sentry에 수집되지 않도록 필터링해야 합니다.

불필요한 에러 로그를 필터링하는 방법은 크게 세 가지가 있습니다.

1. Sentry Inbound Filters
별도로 코드를 배포하지 않고 Sentry 설정을 통해 바로 에러 로그를 필터링할 수 있는 방법입니다. glob 패턴을 사용할 수 있으며, 주로 문자열 기반으로 필터 할 때 적용 가능한 방법입니다. 참고로 Inbound Filters를 통해 필터된 에러 로그는 Sentry 사용량을 소모하지 않습니다. (Sentry의 Inbound Filters의 사용량 정책)

ChunkLoadError: Loading chunk 6169 failed. ChunkLoadError: Loading chunk 127 failed. ChunkLoadError: Loading chunk 7159 failed.

Inbound Filters에서 “ChunkLoadError*”로 필터링했을 때 제외되는 에러

에러의 message로만 필터링할 수 있기 때문에 에러의 다른 필드나 조건을 통한 필터링이 필요한 경우엔 사용하기 어렵지만, 코드를 수정하지 않고도 바로 적용할 수 있어 부득이하게 배포가 불가한 상황에 유용하게 쓸 수 있습니다.

2. Sentry SDK 옵션의 beforeSend

Sentry.init({ beforeSend(event, hint) { const error = hint.originalException if (isAxiosError(error)) { // 401 오류는 수집하지 않는다 if (error.response.status === 401) { return null } } return event }, })

beforeSend는 에러 로그가 Sentry에 전송되기 전 실행되는 이벤트 핸들러 함수입니다. JavaScript로 전송 여부를 제어할 수 있기 때문에, 조건문을 통한 필터링이 가능합니다. 하지만 beforeSend는 config 파일에서 정의되므로 에러가 발생한 지점의 Scope를 활용할 수 없는 단점도 있습니다.

3. 조건부 호출

import { isIOS } from 'utils' function myCaptureException(error: AxiosError) { if (error.code === 'ETIMEDOUT') { return } if (error.code === 'ECONNABORTED') { return } if (navigator.onLine && isIOS && error.code === 'ERR_NETWORK') { return } sentry.captureException(error) }

Sentry SDK의 옵션을 활용한 방법은 아니지만 단순히 captureException 함수를 호출하지 않는 것으로도 필터링이 가능합니다. 에러 객체의 정보뿐만 아니라 Scope의 정보를 활용할 수도 있습니다.

다음으로, 위에서 알아본 방법을 적용하기 위해 Sentry에서 가장 많이 쌓이고 있는 에러 로그 중 네트워크와 연관된 로그를 모아보았습니다.

  1. TypeError: Failed to fetch
  2. TypeError: Load failed
  3. AxiosError: timeout of 10000ms exceeded

참고로 TypeError: Failed to fetch, TypeError: Load failed는 둘 다 fetch 실행 중 네트워크 문제로 인해 발생한 에러입니다. Failed to fetch는 Chrome에서, Load failed는 Safari에서 발생하는 차이가 있어요.

결론부터 말하자면, 이 에러 로그들은 Inbound Filters에 등록하여 수집하지 않도록 했습니다. 이들은 서버 API 응답을 온전히 받지 못하고 오류가 발생한 경우입니다. 클라이언트의 문제일 수도 있고, 혹은 서버 문제일 수도 있습니다. 다만 서버는 별도로 서버팀에서 모니터링하고 있으며, 일반적으로 안정적인 상태이기 때문에 가능성이 낮다고 판단했습니다. 또한, 서버 상태를 감시하더라도 Sentry는 적절한 도구가 아니기 때문에 서버 쪽 문제는 이번 범위에서 제외했습니다.

  • [400 Error] - https://some-api-host.baemin.com/path/{id}
  • [404 Error] - https://some-api-host.baemin.com/path/{id}

한편, Sentry에 기록된 [400 Error]나 [404 Error]와 같은 형태의 에러 로그들은 조금 다른 접근이 필요했습니다. 이들 로그의 대다수가 실제 서비스 운영에 큰 영향이 없다고 파악되었음에도 불구하고, 단순히 HTTP 상태 코드만으로 일괄 필터링하지는 않았습니다. 왜냐하면 특정 API의 응답 자체가 때로는 서비스에 치명적인 장애를 일으키는 원인이 될 수도 있기 때문에, API URL별로 하나씩 상세히 검수하는 과정을 거쳤습니다.

이 에러들의 서비스 영향도를 평가하는 기준은 ‘해당 API 응답(상태 코드 및 응답 내용)이 이미 프론트엔드 비즈니스 플로우 내에서 적절히 핸들링되고 있는가?’였습니다. 일례로, HTTP 상태 코드가 400이면서 응답 내용이 “최대 구매 가능 수량을 초과했어요”인 에러 로그가 Sentry에 다수 적재되고 있었습니다. 이미 해당 상황에 맞춰 사용자에게 토스트 메시지를 띄워주는 방식으로 정상적으로 처리하고 있기 때문에 이 특정 에러는 서비스 영향이 없다고 판단할 수 있었습니다.

이렇게 개별 검수를 통해 Sentry에 적재되지 않아도 되는 에러, 즉 이미 정상적으로 처리되고 있는 에러들의 조건을 식별하고 이를 관리하기 위해 다음과 같은 함수를 만들었습니다. 이 함수는 에러 로그를 Sentry에 적재할지 여부를 최종적으로 결정하는 데 사용합니다.

function shouldSkipErrorLogging(error: AxiosError) { const errorFilters: Array<(error: AxiosError) => boolean> = [ /** * 토스트 메시지를 띄워 처리되고 있음 */ (error) => error?.response?.status === 400 && error?.response?.data === '최대 구매 가능 수량을 초과했어요', /** * 토스트 메시지를 띄워 처리되고 있음 */ (error) => error?.response?.status === 400 && error?.response.data? === '장바구니에 담을 수 있는 수량을 초과했어요' ] return errorFilters.some((filter) => filter(error)) }

Sentry의 Alert이 선제적 장애 탐지로 이어지도록 하자

다음으로는 Alert를 신뢰할 수 있게 만들어 선제적 장애 탐지로 이어지는 것을 달성하기 위한 과정을 설명하려 합니다.

열흘 동안 591건의 Alert가 발생함

위 차트만 보더라도 당시의 Alert Rule에 문제가 있다는 것을 알 수 있습니다. 맞습니다, 이제는 Alert Rule을 최적화할 시간입니다. Alert Rule은 Sentry의 Alerts 탭에서 설정이 가능합니다. 탭 내의 Create Alert 메뉴를 사용하면 되고, 회사에서 사용 중인 Sentry의 플랜에 따라 기능 사용이 제한될 수 있습니다.
앞서 추가했던 정보들을 활용하여 태그 기반으로 이슈를 필터링하거나 Threshold를 설정해 특정 구간별 Alert의 강도를 조절할 수 있습니다.

Alert가 발생하면 여러 Action들을 지정할 수 있는데요, Slack 메신저를 사용 중이라면 Integration을 통해 특정 채널로 메시지를 전송하거나, Email로 Alert을 전달할 수 있습니다.

Alert Rule은 각 사용 사례에 따라 기준에 맞추어 설정하면 됩니다. 하지만 처음부터 정말 다급하게 봐야 하는 “장애” 상황과 조금은 더 여유를 가지고 원인을 파악해서 수정해 나갈 문제를 명확히 구분하는 규칙을 수립하는 것은 어렵습니다. 저희도 지속적으로 에러를 검토하고, 새로운 필터링 규칙을 적용하면서 Alert의 정확도를 높이는 작업을 지속하고 있습니다.

팀 내 Sentry 모니터링 프로세스를 마련하자

위 목표들이 전부 적용되고 나면 중요도가 높은 에러들이 모여, 정확한 정보를 제공하며, 필요한 경우에만 알람이 울립니다. 그렇다면 남은 것은 모니터링된 에러를 처리하는 프로세스를 고도화하는 것입니다.
담당자를 배정하여 알리고, 검토가 진행된 에러 로그에 대해서는 다른 구성원들이 알 수 있도록 하여 중복 검토를 막아야 합니다. 이런 일련의 과정 후에는 검토 결과에 맞는 대응을 해야 하겠죠.

저희가 구상했던 담당자 할당과 모니터링 프로세스를 도식화한 것입니다. 저희는 소통의 대부분을 Slack으로 하고 있습니다. Email보다 훨씬 즉각적이고, 자주 확인하는 툴이라 Slack 메시지로 담당자를 호출하게 했습니다. 간단하지만 효율적으로 동작할 수 있도록 하는 것에 중점을 두었고, 한 명의 담당자를 지정하는 경우 부재중이거나 다른 업무로 바빠 검토가 미루어질 수도 있기 때문에 몇 명씩 묶어 일정 기간 담당하는 것으로 결정하였습니다. 이는 어떤 기능을 누군가 한 명이 전담하지 않고 서로 다 같이 담당하는 저희 팀의 문화가 있어 가능했습니다.
Alert마다 Level을 설정하고, 일정 Level 이상 트리거되면 Slack 봇이 이를 감지하고 Sentry가 남긴 메시지에 아래와 같은 스레드를 생성합니다. 아래는 Block Kit Builder로 생성한 예시입니다.

간단하지만 발생한 Alert을 검토할 담당자에게 에러가 발생했음을 알리고, 주인 의식을 가지고 해당 에러를 검토할 수 있도록 합니다.
검토가 완료된 것을 다른 담당자가 알지 못한다면 중복으로 검토를 수행할 수 있고, 이는 리소스의 낭비로 이어집니다.

이를 방지하기 위해 메시지 내에 “검토완료” 액션 버튼을 누르면 위와 같은 정보를 남겨 어떤 담당자가 언제 검토를 완료했는지 알 수 있도록 하였습니다. 또한 스레드를 확인하지 않고도 이슈가 검토되었는지 확인할 수 있도록 원문 메시지에 체크 이모지를 남기도록 구성하였습니다. 간단하지만 직관적이고, 필요한 정보를 남기도록 하는 데에 중점을 두었습니다.

Alert는 언제든지 발생할 수 있으며, 발생 시 가급적 빠르게 대응해야 합니다. Alert를 모니터링하는 것은 비상시를 항상 대비한다는 것이며, 평시보다 좀 더 높은 긴장감을 유발하며 피로도를 증가시킵니다. 따라서 모니터링은 교대로 담당하는 것이 중요합니다. 또한 규칙은 간단할수록 이해하기 쉽고 잘 이행됩니다.
저희는 스프린트라는 단위 기간으로 이를 구분하기로 하였고, 매 스프린트 시작 시 Sentry 알람 채널에 위와 같은 메시지가 공지됩니다. 이제 교대할 때가 된 거죠.

개선 후 달라진 모습들

지금까지 Sentry를 통한 좀 더 의미 있는 에러 로깅과 사용량 최적화, 그리고 알림 및 담당 체계를 확립하기 위해 저희가 진행했던 활동에 대해서 소개해 드렸습니다.

글을 작성하고 있는 시점 기준으로 이 문제를 인식하고 개선을 진행한 지 한 달 보름 정도가 지나가고 있는데요. 저희의 모습은 어떻게 달라졌을까요? 그리고 이런 활동을 진행할 만큼 가치 있는 효과가 있었을까요? 저희는 분명한 효과와 가치를 확인할 수 있었습니다.

에러가 명확해졌어요

에러가 발생하여 Sentry에 이슈가 적재될 때 제목에 어떤 종류의 에러인지, API 에러라면 어떤 URL에서 에러가 발생한 것인지 기록하여 직관적으로 얻을 수 있는 정보량이 증가했습니다. 제목의 수정이 사용량 최적화나 에러 처리 방식에 직접적인 영향을 준 것은 아니지만, 이후 진행된 모든 최적화와 개선 작업의 속도와 효율성을 높이는 중요한 기반이 되었습니다.

누락되는 에러가 없어졌어요

앞서 말씀드린 것처럼 Sentry는 회사와의 계약에 따라 사용량에 대한 기준이 정해져 있습니다. 특정 기간 내 에러에 대한 로깅 사용량을 모두 소진하면 더 이상 에러가 적재되지 않고, 당연히 관련된 알람도 울리지 않습니다. 또한 사내 정책으로 단위 시간당 적재할 수 있는 에러의 양을 제한하는 정책 등을 적용할 수 있고, 이 경우에는 누락이 더 쉽게 발생할 수 있습니다.

위 차트는 프로젝트에 대한 Rate Limited된 에러의 수를 나타낸 것입니다. 특정 사유(사내 정책으로 인한 단위 시간당 로깅 제한, 사용량 초과 등)로 적재되지 못한 경우를 의미합니다.
빨간 화살표로 표시한 것이 저희의 첫 개선 활동의 시작으로 이후에도 몇 차례에 걸쳐 확인이 필요하다고 판단한 에러에 대해서만 적재하도록 하는 필터 처리나 전송하는 시점에서의 조정 로직이 적용되었습니다.

사용자가 많은 프로젝트의 특성상 에러가 발생할 때에는 동시다발적으로 발생하며, Rate Limited에는 사내 정책으로 인한 단위 시간당 에러 적재에 대한 제한도 포함되어 있기 때문에 실제 에러 발생 시에는 Rate Limited도 같이 증가하게 됩니다. 이를 감안하고 본다면 최적화 활동 이후 눈에 띄게 줄어든 것을 확인할 수 있고, 이는 더 이상 중요한 에러를 놓치지 않고, 발생하는 문제에 민감하게 반응할 수 있는 상태가 되었다는 것을 의미합니다.

위 차트는 같은 기간 Inbound Filters를 통해 필터링된 이슈의 수를 나타낸 것입니다. 팀 내에서 핸들링될 필요가 없다고 판단한 에러들에 대해서는 필터링하여 이를 통해 사용량의 최적화와 검토가 필요한 에러에 집중할 수 있게 되었습니다.

의미있는 Alert 설정

필요한 에러만 적재하게 되면서 효율적인 Alert을 구성할 수 있는 최소 조건을 만족하였습니다. 그리고 여기에 적재된 에러(이슈)들에 대한 적절한 기준을 경험과 논의를 토대로 수립하였습니다. 세분화된 에러를 통해 Alert 역시 목적과 의도를 명확하게 구분할 수 있게 되었고 알람의 Threshold에 대한 기준과 보다 더 세분화된 Alert 정책을 세울 수 있도록 에러를 분석하고, 검토자가 에러에 대해 취득할 수 있는 정보를 추가하는 작업을 지속하고 있습니다.

마치며

대고객 서비스를 제공하는 개발자 입장에서 가장 무서운 순간은 언제일까요? 굉장히 많은 순간들이 떠오를 수 있지만 그중에 고객센터를 통해 인입되는 장애 상황은 반드시 포함될 것입니다.
에러의 발생, 이로 인한 장애의 발생은 물론 없으면 최상이지만, 서비스를 운영하면서 필연적으로 마주치게 되는 것이기도 합니다.

저희는 고객이 겪는 장애 상황의 경중을 떠나, 이를 먼저 인지하고 신속하게 대응하여 고객이 불편함을 느끼지 못하는 것에 목표를 두고 어떻게 이 방법을 달성할 수 있을지를 고민해야 하는데요. 저희가 진행한 이 활동 역시도 궁극적으로 같은 목표를 지향하고 있습니다.

실제로 이렇게 개선된 Sentry Alert과 이슈 최적화 덕분에 몇 건의 에러에 대한 알림을 확인하여 고객에게 문제가 될 만한 상황일 수 있다는 판단을 내렸고, 유관 부서에 이를 전달하여 검토 요청 및 개선을 진행할 수 있도록 하였습니다. 그중에는 고객 입장에서 인지하기 어려운 내부적인 에러도 있었지만 이런 부분들이 장기적으로 부수효과를 발생시켜 예상하지 못한 다른 에러를 만들어내거나, 또 다른 장애로 이어질 수 있는 부분을 빠르게 대응할 수 있었다는 데에 의의가 있습니다.
또한 태그를 사용하여 잘 분류된 이슈들을 통해서 낮은 버전의 브라우저를 사용하는 사용자에게만 발생하는 이슈들을 특정하고 대책을 마련하여 개선한 경험도 해볼 수 있었습니다.

개발을 진행하다 보면 주어지는 비즈니스 과제나 다른 주요 목표들을 달성하기 위해 앞만 보며 달려갈 때가 많습니다. 하지만 보다 좋은 기능을 제공하는 것이나 뛰어난 기술을 적용하는 것만큼이나 중요한 것은 신뢰성과 안정성이 겸비된 서비스입니다. 모두가 잘 알고 있는 사실임에도 이를 지키는 것은 어렵습니다. 먼저, 에러에 대한 정보를 잘 적재하고 이를 전파할 수 있는 수단이 준비되어야 합니다. 또한 누군가 에러에 대해서 검토하고 적극적으로 대응하는 노력도 필요하기 때문입니다.

이 활동을 통해 실제로 잘 정돈된 에러를 적재하는 것이 얼마나 중요한 일인지 알게 되었고, 이를 통해 고객에게 부정적인 영향이 있을 수 있는 부분에 대해서 사전 감지하여 먼저 대응할 수 있다는 것을 체감하였습니다. 그래서 저희가 느낀 경험과 결과에 대해서 팀에 공유하는 것은 물론, 다른 조직에도 선한 영향력을 끼칠 수 있도록 내용을 전달하는 시간을 가졌습니다. 새로운 에러가 발생할 때 유관 부서에 전달하여 예상된 에러인지, 고객에 대한 영향은 없는지 적극적으로 검토해 주시는 동료 구성원들의 모습을 보면서 에러 로그들에 대해 ‘좋은 서비스의 제공을 위해 필수적으로 확인해야 하는 것’이라는 인식이 자리 잡은 것을 느낄 수 있었습니다.

지금까지의 활동들은 약 2개월간 팀 내 외부적으로 진행했던 활동들입니다. 물론 여기서 끝이 아닙니다. 이것을 지속적으로 유지하기 위해서는 구성원들의 관심과 함께 주변 환경 변화에 따른 유지 보수가 필요합니다. 시간이 지나 서비스의 규모가 더욱 커지고 고도화되더라도 지금 했던 이 활동들이 초석이 되어 줄 것을 의심치 않습니다. 더 좋은 서비스를 안정된 환경에서 제공할 수 있도록 최선을 다하겠습니다.

Read Entire Article