Skip to content
isdnetworks
Go back

큐가 준 순서와 처리가 끝나는 순서

(수정됨)

거래소의 잔고 변경 이벤트를 ActiveMQ 큐로 보내는데 순서가 뒤집혀 잔고가 틀리는 일이 있었다. 넣은 순서와 큐가 준 순서는 같았으므로 큐 자체는 순서를 지키고 있었다.

Table of contents

Open Table of contents

원인 — 큐가 준 순서와 끝난 순서

소비하는 Camel 라우트가 여럿이라 각자 하나씩 받아 동시에 처리하고 있었다. 앞의 이벤트가 외부 지갑 호출 때문에 몇 초 걸리는 사이에 뒤의 두 이벤트가 먼저 끝났다.

입금이 반영되기 전에 출금이 먼저 반영되니 중간 balance 가 음수로 찍혔다. 큐가 순서를 줘도 처리가 끝나는 순서는 별개라는 것이 이 문제의 전부였다.

조치 — 같은 대상을 한 소비자에게

두 가지 방법이 있었다. 소비자를 하나로 줄여 전부 직렬로 처리하는 것과, ActiveMQ 의 메시지 그룹으로 같은 계좌의 이벤트만 한 소비자에게 묶는 것이다. 뒤쪽을 골랐다. 이유는 순서를 지켜야 하는 범위가 계좌 하나 안이지 큐 전체가 아니라서다. 대신 소비자가 죽어 그룹이 넘어가는 순간의 중복 처리 위험을 감수했다.

ActiveMQ 공식 문서에 따르면 JMSXGroupID 가 같은 메시지는 그 소비자가 살아 있는 동안 같은 소비자에게 가고, 소비자가 죽으면 다른 소비자가 골라진다. 그래서 JMSXGroupID 를 "acct-" + (accountNo % 16) 으로 붙였다. 다른 계좌끼리는 여전히 병렬로 돌기 때문에 전부를 하나로 처리하는 것보다 훨씬 빠르다.

주의 — 배정이 바뀌는 순간

소비자가 늘거나 줄 때 어떤 그룹의 담당이 바뀐다. 바뀌는 순간에 이전 담당이 처리 중이던 것과 새 담당이 받은 것이 겹칠 수 있었다.

그래서 세션을 SESSION_TRANSACTED 로 두고 처리 중인 것을 끝내고 commit() 한 뒤에만 다음을 받게 했다. 커밋 전에 넘어가면 다음 소비자가 같은 이벤트를 다시 처리하게 된다.

순서가 필요한 것만 고른다

이벤트 종류를 훑어보니 순서가 결과를 바꾸는 것은 잔고 변경뿐이었고 알림과 통계는 순서와 무관했다. 전부 그룹으로 묶으면 병렬성이 사라져 처리량이 크게 준다.

순서를 지키는 것은 공짜가 아니므로 그것이 결과를 바꾸는 종류만 골라서 적용했다. 나머지는 JMSXGroupID 없이 그대로 병렬로 두었다.

순번과 쏠림

이벤트에 seq 를 붙여서 이미 반영된 것보다 낮은 순번이 오면 버리게 했고 그룹이 순서를 지키는 위에 한 겹을 더 둔 셈이다. seq 가 안 이어지는 횟수를 지표로 세니 그룹을 나눈 뒤에는 0이 됐고 소비자를 늘린 날에만 잠깐 올라갔다.

그룹별 대기 건수를 보니 한쪽에 몰려 있었는데 활동이 많은 계좌가 그 그룹에 걸려 있었다. 나누는 기준이 고르게 나뉘는지도 함께 봐야 했고 % 16 을 % 64 로 늘려 흩으니 나아졌다.

정리


Share this post on:

Previous Post
실제로 돌려 본 보안 점검 목록
Next Post
못 읽는 형식은 변환해서 읽는다