Skip to content
isdnetworks
Go back

수만 건을 한꺼번에 바꾸지 않고 나눠서 하는 법

(수정됨)

수만 건을 외부 시스템에 다시 보내야 했는데 한 번에 돌리면 중간에 실패했을 때 어디까지 됐는지 알 수 없었다. 나눠서 하기로 했지만 나누는 것만으로는 부족했다.

Table of contents

Open Table of contents

문제 — 대상의 고정

제일 먼저 부딪힌 것이 대상을 어떻게 잡느냐였다. 처음에는 아직 처리 안 된 것이라는 조건으로 잡았는데 처리하면 그 조건에서 빠진다는 문제가 있었다.

진행률을 재려고 다시 세면 숫자가 줄고 성공할수록 대상이 사라지니 아무것도 안 된 것처럼 보인다. 그래서 시작할 때 대상 행에 wave_no를 UPDATE로 찍어 고정하고 그다음부터는 WHERE wave_no = N으로 대상을 지정했다. 조건이 바뀌어도 대상이 안 바뀌는 것이 이 방식의 핵심이다.

여기서도 실수를 했는데 조건에 다른 경로로 이미 완료된 것이 빠져 있어서 그것들이 계속 후보로 뽑혔다. status만 보고 external_id는 안 봤기 때문이었고 조건에 external_id IS NULL을 추가하고 나서야 정리됐다.

판단 기준 — 웨이브 크기와 실패율

50건씩과 100건씩을 각각 돌려 보니 실패율이 달랐다. 50건일 때 한 자릿수 퍼센트였고 100건일 때 그 두 배쯤이었다.

크게 잡을수록 처리 시간이 길어지면서 timeout에 걸리거나 Lock wait timeout 경합이 나기 때문이었다. 두 가지 방법이 있었는데 크기를 고정값으로 정하는 것과 실패율을 보면서 조절하는 것이다. 뒤쪽을 골랐다. 이유는 시간 초과와 잠금 경합이 상대 쪽 부하에 따라 달라져 고정값이 오늘 맞아도 내일 안 맞아서다. 대신 웨이브마다 실패율을 사람이 보고 크기를 정하는 수고를 감수했다.

검증 — 응답을 믿지 않는 판정

여기가 가장 중요했다. 호출이 성공 응답을 돌려주고 실패 목록이 비어 있어도 실제로는 안 된 것이 있었는데 내부 검증에서 걸리면 아무것도 반환하지 않고 조용히 건너뛰는 경로가 있었다.

그래서 성공 판정을 데이터로 한다. 웨이브별 COUNT(*)와 status = 'sent' 건수와 external_id IS NULL 건수를 함께 세면 응답이 무엇이라고 하든 실제 상태가 나온다. 응답은 요청이 접수됐다는 것이지 처리됐다는 증거가 아니다.

주의 — 끊겼을 때의 재시도

한 번은 앞단 프록시가 502로 끊겨서 응답을 아예 못 받았는데 이때는 재시도하면 안 된다. 응답을 못 받은 것과 처리가 안 된 것은 다르고 뒤에서는 이미 접수했을 수 있어서 그대로 다시 보내면 중복이 생긴다.

그래서 그 웨이브의 실제 처리량을 세고 안 된 것만 골라 그것만 다시 보낸다. 한 번은 100건 중 한 자릿수 건만 실제 미처리였고 전량 재전송했으면 90여 건이 중복됐을 것이다.

조치 — 실패 사유의 분류

실패한 것을 그냥 다시 보내면 대개 또 실패해서 사유별로 갈랐다. timeout이나 잠금 경합 같은 일시적 문제는 다시 하면 되고 필수값 NULL이나 형식 불일치 같은 데이터 문제는 데이터를 고쳐야 한다.

상대가 거부하는 외부 문제는 우리가 못 고치고 애초에 보내면 안 되는 대상 아님이 나오면 대상 선정으로 돌아가야 한다. 실제로 한 번은 한 번도 등록된 적 없는 것이 대상에 섞여 있었다.

웨이브마다 건수와 성공과 실패와 사유별 내역을 기록으로 남겼다. 다음 웨이브 크기를 정하는 근거가 되고 나중에 개별 건의 경위를 추적할 수 있다. 기록을 안 남기고 진행했다가 며칠 뒤에 처음부터 다시 조사한 적이 있다.

정리


Share this post on:

Previous Post
다른 키를 먼저 세어 본다
Next Post
알려진 버그부터 검색한다