Skip to content
isdnetworks
Go back

조용히 실패하는 연동

외부에 자료를 보내는 연동이 있었다. 며칠치가 상대 쪽에 없다는 연락을 받았는데 우리 로그에는 아무 오류가 없었다.

Table of contents

Open Table of contents

증상 — 오류가 없던 로그

오류가 없는데 자료가 안 갔다면 실패를 기록하지 않은 것이다. 그래서 sendToPartner 를 부르는 자리를 열어 실패했을 때 무엇을 하는지부터 봤다.

grep 으로 그 기간의 로그를 다시 훑어도 경고 한 줄이 없었다. 아무것도 없는 것과 아무 일도 없었던 것을 로그만 보고는 가를 수 없었다.

받은 연락에는 빠진 날짜가 적혀 있어서 그 날짜의 주문은 우리 쪽에 남아 있었다. orders 에는 정상으로 들어갔고 밖으로 나가는 부분만 빠진 상태였다.

원인 — 예외를 삼키던 catch

전송 코드는 이렇게 돼 있었다.

try {
    $this->sendToPartner($data);
} catch (Exception $e) {
    // 전송 실패해도 주문 처리는 계속되어야 함
}

주석에 적힌 의도 자체는 맞다. 외부 전송이 실패했다고 주문 처리를 막으면 안 된다.

문제는 catch 안에 그 주석 말고 아무것도 없다는 점이었다. 실패했다는 사실이 $e 와 함께 그 자리에서 사라진다.

흐름을 계속하는 것과 아무 일도 안 하는 것은 다른 이야기다. 이 둘이 한 덩어리로 붙어 있어서 앞을 택하면 뒤가 딸려 오고 있었다.

조치 — 흐름과 기록과 재처리를 나눴다

정해야 할 것이 하나가 아니라 셋이었다. 흐름을 막을 것인지와 기록을 남길 것인지와 나중에 다시 보낼 것인지다.

이번 경우 sendToPartner 에 대한 답은 차례로 아니오와 예와 예였다. 원래 코드는 첫 번째만 답하고 나머지 둘을 답하지 않은 상태였다.

셋을 따로 적어 넣었다.

try {
    $this->sendToPartner($data);
} catch (Exception $e) {
    Log::error('partner send failed', [
        'order_id' => $data['id'],
        'exception' => $e,
    ]);
    PendingTransfer::create([
        'order_id' => $data['id'],
        'payload'  => $data,
        'attempts' => 0,
    ]);
}

흐름은 그대로 계속되고 Log::error 가 남고 PendingTransfer 에 재시도 대상이 쌓인다.

배치가 주기적으로 PendingTransfer 를 꺼내 다시 보내고 attempts 를 올린다. 몇 번 실패하면 상태를 바꾸고 사람에게 알린다.

재시도가 안전한지 확인했다

다시 보내는 처리를 넣기 전에 확인할 것이 하나 있었다. 같은 자료를 두 번 보내면 상대 쪽에서 어떻게 되는가였다.

물어보니 주문번호로 중복을 거른다고 해서 두 번째는 무시된다고 했다. 그 답을 문서에 적어 두었는데 이런 것은 물어봐야 알고 물어본 사람이 떠나면 다시 물어야 한다.

중복을 안 걸렀으면 우리 쪽에서 전송 여부를 order_id 별로 기록하고 그것을 확인한 뒤에 보내야 했다. 재시도를 넣을 수 있느냐는 상대 쪽 동작이 정하는 것이었다.

설정 — 등급 나누기와 성공 건수

기록을 넣으면서 어떤 것을 어느 등급으로 남길지도 정했다.

상황레벨이유
한 번 실패, 재시도 예정warning흔하고 스스로 회복된다
재시도 소진error사람이 봐야 한다
응답이 예상과 다름error계약이 바뀌었을 수 있다

전부 error 로 하면 진짜 문제가 그 안에 묻힌다. 하루에 수십 건 나는 일시적 실패와 재시도가 다 끝난 실패는 성격이 다르다.

그리고 실패만 남기면 아무것도 안 나갔을 때를 알 수 없다. 배치가 아예 안 돌면 실패 로그도 안 생겨서 조용한 것이 정상인지 고장인지 갈리지 않는다.

그래서 성공 건수도 주기적으로 남겼다.

[transfer] 배치 완료: 성공 1,240 / 실패 3 / 대기 12

0건이어도 이 줄은 나간다. 이 줄이 안 보이면 배치가 안 돈 것이다.

우리가 아니라 상대가 알려줬다

빠진 건수를 세어 보니 연락받은 며칠치가 전부가 아니었다. 오래된 것도 섞여 있었고 그때는 상대 쪽에서 알아채지 못했을 뿐이었다.

이 사건에서 가장 걸린 것은 삼킨 예외 자체가 아니었다. 우리 시스템의 실패를 우리가 아니라 상대가 먼저 알려줬다는 쪽이었다.

그 구조면 상대가 보는 것만 결국 알게 된다. 상대가 안 보는 연동에서 같은 catch 를 만나면 아무도 모르는 채로 남는다.

정리


Share this post on:

Previous Post
같은 쿼리가 운영에서만 느렸다
Next Post
오래된 코드에 새 패턴 넣기