Skip to content
isdnetworks
Go back

누구 책임인지를 나타내는 값을 정의했다

(수정됨)

연동 실패가 쌓이는데 우리 문제인지 상대 문제인지 매번 열어 봐야 알았다.

Table of contents

Open Table of contents

증상 — 이유가 문자열로만 있었다

fail_reason 으로 묶어 봤다.

SELECT fail_reason, COUNT(*) FROM sync_log WHERE result='FAIL' GROUP BY fail_reason;
Connection timed out                      412
Invalid parameter: courseId is required    88
SQLSTATE[23000]: Duplicate entry           41
cURL error 6: Could not resolve host        22

메시지가 제각각이라 GROUP BY 로 묶이지 않았다.

어느 것이 누구 몫인지도 한눈에 안 보였다. 상대 쪽 메시지가 바뀌면 그동안 묶어 두던 규칙이 한꺼번에 깨진다는 것도 있었다.

조치 — 책임 소재를 값으로 뒀다

sync_log 에 컬럼을 하나 더했다.

ALTER TABLE sync_log ADD COLUMN fault_side varchar(10);
OURS      우리 코드나 데이터 문제
THEIRS    상대 시스템 문제
NETWORK   중간 구간 문제
UNKNOWN   판단 못 함

fault_side 에 네 값 중 하나가 들어간다.

if     ($e instanceof ValidationException) { $side = 'OURS'; }
elseif ($e instanceof HttpServerError)     { $side = 'THEIRS'; }
elseif ($e instanceof ConnectException)    { $side = 'NETWORK'; }
else                                       { $side = 'UNKNOWN'; }

메시지 문자열을 보는 것과 예외 종류로 보는 것 두 가지 방법 중 뒤쪽을 골랐다. 이유는 클래스로 봐야 상대 메시지가 바뀌어도 안 흔들려서다.

결과 — 분류별 건수

같은 기간을 fault_side 로 다시 묶었다.

SELECT fault_side, COUNT(*) FROM sync_log WHERE result='FAIL' AND at >= ? GROUP BY fault_side;
NETWORK   412
OURS       88
THEIRS     41
UNKNOWN    22

NETWORK 가 가장 많아서 우리 코드를 아무리 봐도 안 나올 것이었다.

어디를 봐야 하는지가 이 숫자로 갈렸다. 우리 쪽으로 분류된 것은 우리가 고칠 수 있고 상대 쪽은 알려야 하는 것이라 해야 할 일이 다르다.

대응 — 받는 사람별 알림

fault_side 별로 알림도 나눠 보냈다.

if ($ourFails > 10) {
    notify('개발팀', "우리 쪽 원인 연동 실패 {$ourFails}건");
}
if ($theirFails > 50) {
    notify('운영팀', "상대 시스템 오류 {$theirFails}건. 상대에 문의 필요");
}

받는 사람이 달랐는데 전에는 전부 한 곳으로 가서 대부분 그냥 넘어갔다.

UNKNOWN 은 처음 스무 건 남짓이었고 열어 보면서 분류를 늘렸다.

if (strpos($msg, 'Duplicate entry') !== false) { return 'OURS'; }
if (strpos($msg, 'rate limit') !== false)      { return 'THEIRS'; }

몇 주에 걸쳐 UNKNOWN 이 0에 가까워졌고 새 UNKNOWN 이 나오면 알림이 가게 했다.

THEIRS 를 상대에게 알릴 근거도 생겼다.

2019-10-01 ~ 2019-10-15
  상대 시스템 5xx 응답: 412건
  주요 시각: 매일 02:00~03:00

숫자와 시각을 정리해 보내니 가끔 안 된다보다 이야기가 빨랐고 상대 쪽 배치가 그 시간에 돌면서 응답이 느려지는 것이었다.

예방 — 보내기 전에 거르기

OURS 로 분류된 것부터 줄였다.

OURS 88건
  필수 항목 누락  61
  중복 전송       19
  형식 오류        8

필수 항목 누락이 대부분이라 보내기 전에 확인을 넣었다.

$missing = array_diff($this->requiredFields(), array_keys(array_filter($payload)));
if ($missing) {
    throw new ValidationException('필수 항목 없음: ' . implode(',', $missing));
}

requiredFields 로 먼저 걸리니 실패가 아니라 안 보내는 것이 됐다.

상대에게 부담도 안 주고 수십 건이 십여 건이 됐다. 재시도 정책도 갈렸는데 우리 문제는 다시 보내도 안 되고 코드를 고쳐야 한다.

$retryable = in_array($side, ['NETWORK', 'THEIRS'], true);

전에는 전부 재시도해서 같은 것을 다섯 번씩 실패하고 있었다.

주의 — 틀릴 수 있는 판단

Exception 종류로 판단하니 대부분 맞았지만 틀리는 경우가 있었다.

상대가 400 을 주는데 실제로는 우리 요청이 아니라 상대 쪽 규격이 바뀐 것이었다.

if ($status === 400) {
    // 우리 요청이 잘못됐거나 상대 규격이 바뀌었다
    return $this->recentSuccessRate() > 0.9 ? 'THEIRS' : 'OURS';
}

조금 전까지 잘 되던 것이 갑자기 400 이면 상대가 바뀐 쪽에 가깝다.

계속 400 이었으면 우리 문제다. 완벽하지는 않지만 처음 볼 곳을 정하는 데는 충분했고 틀렸을 때 값을 다시 고칠 수 있게 뒀다.

정리


Share this post on:

Previous Post
열 개였던 쿼리가 하나였다
Next Post
단방향 동기화와 반대편의 0