시스템 설계: 최악의 상황에 대비

Nov 28 2022
새 응용 프로그램이나 새 기능을 개발할 때 주의해야 할 중요한 사항 중 하나는 오류가 발생한 경우 응용 프로그램이 자체 복구하는 기능입니다. 귀하의 코드는 훌륭하고 비즈니스 요구 사항을 충족할 수 있지만 예기치 않은 오류가 발생하는 경우에도 수행하기에 충분합니까? 이러한 오류를 자동으로 극복할 수 있을 만큼 애플리케이션 복원력이 충분합니까? 처리할 것으로 예상되는 시나리오가 매우 기술적이고 개발 중심적일 수 있으므로 비즈니스 요구 사항의 일부로 복원력 및 폴백 메커니즘을 구현하도록 암시적으로 지시되지 않는 경우가 있습니다.
Unsplash의 David Pupaza 사진

새 응용 프로그램이나 새 기능을 개발할 때 주의해야 할 중요한 사항 중 하나는 오류가 발생한 경우 응용 프로그램이 자체 복구하는 기능입니다. 귀하의 코드는 훌륭하고 비즈니스 요구 사항을 충족할 수 있지만 예기치 않은 오류가 발생하는 경우에도 수행하기에 충분합니까? 이러한 오류를 자동으로 극복할 수 있을 만큼 애플리케이션 복원력이 충분합니까?

처리할 것으로 예상되는 시나리오가 매우 기술적이고 개발 중심적일 수 있으므로 비즈니스 요구 사항의 일부로 복원력 및 폴백 메커니즘을 구현하도록 암시적으로 지시되지 않는 경우가 있습니다. 비기능적 요구사항 계열과 관련된 메커니즘.

우리가 말하는 "예기치 않은 실패"란 무엇입니까?

응용 프로그램을 오류/불안정한 상태로 만드는 다양한 시나리오가 발생할 수 있습니다. 예를 들어:

  • 애플리케이션에서 사용 중인 외부 API의 시간 초과/서비스 사용 불가
  • 애플리케이션에서 사용 중인 타사의 버그
  • 데이터베이스 작업 실패
  • 성능 저하를 유발하여 들어오는 요청을 거부하는 애플리케이션의 예기치 않은 로드
  • 데이터 손실을 일으키는 예기치 않은 오류
  • 응용 프로그램에서 관리하는 수명이 긴 프로세스가 데이터 손실로 인해 해당 상태에서 멈춤

관련 자가 복구 및 폴백 메커니즘이 없는 애플리케이션은 장기적으로 사용할 수 없습니다. 이러한 "예기치 않은 실패"는 결국 발생하여 SLA 및 고객 경험을 손상시킬 것입니다. 복구하기 어려운 데이터 손실이 발생할 수 있으며 문제를 해결하기 위해 수동 으로 개입하고 일부 마법을 수행해야 합니다. 이제 분당 수십만 건의 요청을 처리하는 애플리케이션에 대해 생각해 보십시오. 작은 오류가 큰 영향을 미칠 수 있으므로 수동으로 수정하기 어려울 수 있으며 조직에 많은 시간과 비용이 소요됩니다.
그러므로 최악의 상황에 대비해야 합니다.

몇 가지 일반적인 시나리오를 살펴보고 애플리케이션 복원력 수준과 폴백 기능을 향상시켜 밤에 편히 잘 수 있는 솔루션을 제공하겠습니다. :)

재시도 메커니즘

특히 http 요청 전송, 데이터베이스 또는 파일 읽기/쓰기 등과 같은 I/O 작업을 처리할 때 오류가 발생할 가능성이 일반적입니다. 예: 서비스 사용 불가, 내부 서버 오류, 작업 시간 초과 등.
이러한 많은 오류는 일시적일 수 있으며 몇 초 만에 중지될 수 있습니다. 즉, 몇 초 후에 다음 시도가 성공할 가능성이 높습니다.
그렇다면… 왜 작은 일시적인 문제로 인해 비즈니스 흐름이 실패할까요? 다시 시도하십시오! 작업에 대한 재시도 메커니즘을 사용하여 문제가 일시적인 경우 애플리케이션이 자동으로 문제를 극복하도록 합니다.
다음에 주의하십시오.

  • 애플리케이션이 실행을 시도하는 재시도 횟수를 구성하고 제한할 수 있습니다. 무한/높은 재시도 횟수로 인해 애플리케이션이 중단되고 끝없는 재시도로 바쁘기 때문에 다른 요청을 거부할 수 있습니다.
  • 지수 베이크오프를 구현합니다. 재시도 사이의 간격을 기하급수적으로 늘립니다.
    일시적인 문제(서버/데이터베이스/타사)가 해결되고 다음 시도가 성공할 가능성이 높아집니다. 또한 지수 간격을 사용하면 부하가 걸려 클라이언트에 서비스를 제공할 수 없는 제3자에게 유예를 제공합니다. 지연 없이 즉시 재시도를 실행하면 문제가 악화될 수 있습니다.
  • 재시도해야 하는 오류와 그렇지 않은 오류를 이해해야 합니다. 다른 재시도로 모든 오류를 해결할 수 있는 것은 아닙니다. 예를 들어 http 요청이 '잘못된 요청'(400) 상태 코드로 실패했다고 가정해 보겠습니다. 요청 데이터가 있는 항목이 잘못되어 서버에서 거부되었음을 의미합니다. 애플리케이션이 얼마나 많은 재시도를 하든 상관없이 동일한 결과인 실패로 종료됩니다.
    이러한 종류의 오류에 대한 재시도는 중복되며 애플리케이션 성능에 영향을 미칩니다. 서로 다른 오류를 구분하고 재시도할 수 있는 항목을 결정해야 합니다.

처음부터 작성할 필요가 없도록 재시도 메커니즘의 모든 관련 기능을 이미 제공하는 여러 오픈 소스 라이브러리가 있습니다. 예를 들어 .NET에는 Polly라는 라이브러리가 있습니다.

모든 재시도가 실패하면 어떻게 해야 합니까?라는 질문을 하는 것을 잊지 마십시오. 이것은 비즈니스 질문이며 대답은 시나리오 간에 변경될 수 있습니다.

프로세스에 대한 제한 시간 정의

일부 응용 프로그램은 수명이 긴 프로세스를 관리합니다. 프로세스에는 진행 상황을 나타내는 상태가 있습니다. 프로세스의 진행은 때때로 긴 작업 또는 타사의 비동기 응답에 따라 달라집니다. 일이 잘못될 수 있기 때문에(항상 잘못되기 때문에) 프로세스가 최종 상태가 아닌 상태에서 영원히 멈춰 "열린" 상태를 유지할 수 있습니다. 예를 들어 제3자가 응답을 다시 보내는 데 실패했습니다.

좋아, 그래서 그들은 열려있을 것입니다. 무슨 일이야?
다른 시스템이 귀하의 프로세스에 의존하고 있으며 결과를 기다리고 있을 가능성이 높습니다. 애플리케이션이 전체 비즈니스 흐름을 방해하여 다른 서비스 SLA 및 사용자 경험에 영향을 미칩니다.
또한 이러한 공개 프로세스는 다르게 작동하고 통계에 해를 끼칠 수 있으므로 조사 및 데이터 분석을 어렵게 만듭니다.

나는 이것을 원하지 않는다! 어떻게 해결합니까?
먼저 프로세스가 최종 상태가 아닌 상태를 유지할 수 있는 적합하고 허용 가능한 시간을 정의합니다. 이것은 비즈니스 질문이며 의무가 있는 SLA와 프로세스 내부 작업에 소요되는 합당한 시간을 평가하여 결정해야 합니다. 예를 들어 내 프로세스가 평균 응답 시간이 10분인 타사에 의존하는 경우 제한 시간을 20–30분으로 정의할 수 있습니다.
이제 구현을 위해 이동할 수 있습니다. 기본 구현은 X분마다 실행되고 Y분(시간 초과 값)보다 긴 열린 프로세스를 검색하는 예약된 작업을 만드는 것일 수 있습니다. 발견되면 프로세스를 최종 상태로 이동하고 실패 상태/취소 상태가 될 수 있습니다.
이렇게 하면 프로세스를 정상적으로 실패/취소할 수 있습니다. 그것은 관리 가능한 방식으로 수행되고 있으며 응용 프로그램이 관련 결과를 대기 중인 클라이언트에 알릴 수 있는 기능을 제공합니다.

알아채다! 패턴은 문제 자체를 해결하지 못했지만 자동으로 문제를 관리하고 대체 흐름을 실행할 수 있는 기능을 제공했습니다. 문제가 있음을 알리고 약속된 SLA를 준수할 수 있습니다.
이 패턴을 사용하는 실제 시나리오를 살펴보겠습니다.
Payoneer의 제 팀은 고객 문서의 자동 승인 프로세스를 담당합니다. 문서의 승인 프로세스는 여러 단계로 구성되는 오프라인 프로세스이며 비동기 통신을 통해 분석 결과를 제공하는 외부 벤더와 통합됩니다.
당사 시스템은 시간 초과 패턴을 구현하고 시간 초과 시 자동 결과를 기다리는 대신 담당자가 고객의 문서를 수동으로 검토하도록 시스템을 이동합니다.
이렇게 하면 문서를 검토하는 데 걸리는 시간과 관련하여 고객 앞에 있는 SLA에 영향을 미치지 않습니다.

콜백/웹후크에서만 릴레이하지 않음 - 폴링 메커니즘 구현

비동기 통신을 처리할 때 응답은 콜백으로 클라이언트에 반환됩니다. 데이터로 웹후크를 트리거하는 서버이거나 이벤트 버스/메시지 브로커를 통해 이벤트를 게시하는 서버일 수 있습니다.
메시지가 클라이언트에 의해 수신되지 않았거나 수신되었지만 올바르게 처리되지 않아 삭제되어 데이터 손실이 발생하는 시나리오가 있습니다. 이러한 시나리오에서 클라이언트는 여전히 응답을 보류하고 있지만 서버는 이미 응답을 보냈으며 다시 보내지 않습니다.

이전 섹션에서 우리는 문제를 해결하는 것이 아니라 정상적으로 실패하고 관리 가능한 폴백 흐름을 활성화하는 시간 제한 패턴에 대해 언급했습니다. 폴링 메커니즘을 구현하면 일부 문제를 자동으로 해결하고 비즈니스 흐름의 "행복한 경로"를 유지할 수 있습니다.

서버가 푸시를 통해 비동기식으로 응답을 제공하기를 기다리는 대신 X분마다 실행되고 Y분 이상 보류 중인 요청을 검색하는 예약 프로세스를 구현합니다. 이러한 요청에 대해 애플리케이션은 응답 데이터를 얻기 위해 서버에 동기식 요청을 보냅니다. 원래 요청이 서버에서 처리를 마친 경우 결과를 보낼 수 있습니다. 이 경우 웹후크에서 데이터를 수신한 경우와 동일하게 애플리케이션이 비즈니스 흐름을 계속합니다. 그렇지 않으면 원래 요청이 여전히 서버에서 처리되고 있으며 다시 보내지 않아도 됩니다. 계속 기다리기만 하면 됩니다.

이 메커니즘을 구현함으로써 비동기 통신과 관련된 예기치 않은 문제를 자동으로 해결할 수 있었고 흐름 실패를 방지하거나 서버에서 데이터를 복원하기 위해 수동으로 개입할 수 있었습니다.

알아채다! 통합할 모든 서비스가 제공하는 비동기식 통신 외에 폴링을 위한 엔드포인트를 제공하는 기능이 있는 것은 아닙니다. 그들이 가지고 있지 않다면 미래에 할 수 있는지 물어볼 가치가 있습니다.

이 솔루션에는 성능 오버헤드가 있는 것이 사실이지만 올바른 간격을 선택하면 원활하게 작동하고 장애 발생 시 제공하는 이점은 값을 매길 수 없습니다.

공급업체 잠금 방지

애플리케이션이 비즈니스 목표를 달성하기 위해 외부 공급업체를 사용하는 것이 일반적입니다. 특히 논리가 복잡하고 조직의 주요 비즈니스에서 멀리 떨어져 있는 경우 모든 조직이 사내에서 모든 논리를 개발하는 것은 불가능합니다. 따라서 비즈니스 흐름을 완료하기 위해 필요한 논리의 일부를 제공할 수 있는 공급업체와 통합하는 것을 선호할 것입니다.

따라서 조사 및 POC 후에 귀하의 필요에 적합한 공급업체를 찾았습니다.
통합은 순조롭게 진행되었고... 짜잔! 비즈니스 흐름을 시작하고 하루에 수십만 명의 고객에게 서비스를 제공합니다.

이제 질문을 드리겠습니다. 언젠가 이 외부 벤더가 작동을 멈추면 어떻게 될까요? 파산하면 어떻게 될까요? 해킹을 당해 장기간 서비스를 이용할 수 없다면? 아니면 공급업체의 성능에 만족하지 못하거나 매월 개발 팀이 통합을 조정하기 위해 24시간 작업해야 하는 새로운 주요 변경 사항이 있기 때문에 관리되는 방식에 만족하지 못할 수도 있습니다.

특히 중요하고 중요한 비즈니스 흐름을 처리할 때 이 시나리오는 매우 중요하며 조직의 일부를 마비시키고 고객에게 막대한 영향을 미칠 수 있습니다.
다시 Payoneer 팀에서 관리하는 고객 문서의 자동 승인 프로세스로 돌아가서 프로세스의 일부로 문서에서 관련 텍스트를 추출하고 처리하기 위해 외부 공급업체를 사용하고 있습니다. 우리는 이 결과를 제공하고 그들 사이에서 우선 순위를 갖는 2개의 공급업체와 통합하기로 선택했습니다. 따라서 하나가 실패하거나 잘못된 긍정이 많으면 비즈니스 흐름의 중단 시간 없이 다른 하나로 전환할 수 있습니다. 물론 이것은 또한 그들 중 하나에 어떤 일이 발생하면 우리는 항상 전달할 다른 사람이 있다는 확신을 줍니다.

물론 항상 이 백업을 만들 필요는 없습니다. 돈에 대한 가치와 처리하고 있는 비즈니스 흐름이 얼마나 중요한지에 대한 질문입니다.

회로 차단기 패턴

일시적인 오류를 해결한다고 가정하는 재시도 패턴과 달리 서킷 브레이크 패턴은 애플리케이션이 실패할 가능성이 높은 작업을 호출하지 못하도록 방지하기 위한 것입니다.
현재 모든 요청이 실패하는 버그가 있는 REST API를 통해 타사를 사용하고 있다고 생각하십시오. 수리를 위한 ETA가 4시간이라고 가정해 보겠습니다.
재시도 패턴을 사용하면 애플리케이션은 결국 실패할 요청을 호출하기 위해 여러 번 시도합니다. 이러한 쓸모 없는 재시도 시도는 애플리케이션 및 타사의 성능에 영향을 미칩니다.
그 대신 회로 중단 패턴을 사용하면 애플리케이션이 빠르게 실패하고 문제가 해결되면 점진적으로 복구를 시도할 수 있습니다. 이를 통해 오류 발생 및 성능 저하를 방지할 수 있습니다.

어떻게 작동합니까?
메커니즘은 실행하려는 작업에 대한 프록시 역할을 합니다.
3가지 상태가 있습니다.

  • 닫힘- 작업이 정기적으로 실행될 수 있음을 의미합니다.
    이 상태에서 실패 횟수가 카운트됩니다. 실패 횟수가 정의된 시간 간격에서 정의된 임계값을 초과하면 메커니즘이 열린 상태로 이동합니다.
  • 열기 - 메커니즘이 많은 실패를 경험했기 때문에 실행되는 작업을 방지하므로 실행을 시도하지 않고 빠르게 실패하는 것을 선호합니다. 이 상태에서는 시간 초과가 있습니다. 도달하면 메커니즘이 반 열림 상태로 이동합니다.
  • Half open- 작업 실행에 대한 제한된 요청이 허용되고 전달됨을 의미합니다. 이 상태는 문제가 해결되었는지 확인하기 위한 상태입니다. 이러한 제한된 양의 작업이 성공적으로 실행되면 메커니즘은 문제가 해결되었다고 가정하고 메커니즘의 상태를 닫힘으로 이동합니다. 그렇지 않으면 문제가 여전히 존재하므로 Open으로 다시 이동합니다.

쉽고 효율적인 수동 개입을 위한 전용 엔드포인트

모든 것을 말했지만 때때로 우리는 발생할 수 있는 모든 경우에 대해 자동화 솔루션으로 준비되지 않았습니다. 때로는 시나리오를 처음 접하고 자동화로 해결하는 방법을 이해한 후에만 자동 솔루션이 제공됩니다. 따라서 이러한 경우 수동 개입이 불가피합니다. 대부분의 경우 이러한 수동 개입은 데이터베이스를 통해 직접 값을 변경하거나 수동으로 메시지를 재구성하고 메시지 브로커를 통해 직접 전송하는 것과 같은 서비스의 "내장"을 거쳐야 합니다.

또한 여기서 우리는 이 수동 개입이 관리 가능하고 안전하며 효율적일 수 있도록 미리 준비할 수 있습니다. 일반적인 수동 작업을 가능하게 하는 내부 API를 애플리케이션에 생성하기만 하면 됩니다.
예를 들어 애플리케이션이 관리하는 프로세스를 취소할 수 있는 엔드포인트를 생성합니다. 또는 이전 프로세스의 결과를 다시 게시할 수 있는 엔드포인트를 생성하여 손실되거나 실행되지 않은 경우 다른 시스템에서 다시 사용할 수 있도록 합니다.

이 내부 API를 생성하면 누군가 트리거해야 하지만 많은 이점이 있습니다.

  • 작업은 응용 프로그램에서 관리할 수 있습니다. 응용 프로그램은 변경 사항의 소유자이며 이를 인식합니다.
  • 작업은 개발의 일부였기 때문에 QA에서 테스트를 거쳤습니다. 따라서 실수할 가능성을 줄입니다.
  • 때로는 작업이 DB의 값을 업데이트하는 것만큼 간단하지 않습니다. 때로는 수행해야 할 부수적 조치도 있습니다. 이러한 모든 사이드 액션은 쉽게 사용할 수 있도록 내부 API에서 관리할 수 있습니다.
  • 시간 절약

결론적으로

귀하의 지원서는 항상 최악의 상황에 대비해야 합니다!
애플리케이션이 자가 복구 및 폴백 솔루션에 대해 더 많이 알게 될수록 성능이 향상되고 고객 경험에 미치는 영향이 낮아질수록 장애를 더 빨리 극복하고 조직의 귀중한 시간과 비용을 절약할 수 있습니다.

애플리케이션/기능 설계 중에 이에 주의를 기울여야 합니다. 관련 치료를 준비하고 생산 과정에서 불쾌한 놀라움을 피하려면 올바른 질문을 하고 약점을 식별해야 합니다.