2년 전에 만들어진 행이 지금 모순을 만들었다
미납 금액이 남은 금액보다 큰 논리적 모순을 조사하다, 2년 전 정정 작업이 금액만 되돌리고 행은 남겨 둔 흔적을 검산으로 복원한 과정을 적었다.
리스크 관리 쪽에서 요청이 왔다. 한 판매자의 상세 화면에서 미납 금액이 남은 금액보다 크게 나온다는 것이었다. 미납은 남은 금액의 일부이므로 아직 안 낸 것이 낼 것보다 많을 수는 없다. 숫자가 조금 틀린 정도가 아니라 애초에 성립할 수 없는 상태였다.
정정 요청을 바로 받지 않았다
요청은 구체적이었다. 실제 남은 금액이 얼마이니 미납을 그 값으로 맞춰 달라는 것이었다. 지적은 정확했고 계산도 맞았다. 그래도 바로 고치지 않았다. 값을 맞추면 화면은 정상으로 보이지만, 왜 그렇게 됐는지 모른 채 덮으면 같은 일이 다른 판매자에게도 있는지 알 수 없다.
먼저 확인할 것은 하나, 이 값이 어디서 왔는가였다.
성립해야 할 식을 먼저 적었다
이 도메인에는 항상 맞아야 하는 관계가 있다. 회수 대상을 만들 때 원금에서 그 금액을 미리 빼 두고, 실제로 회수될 때 남은 금액이 줄어든다. 그래서 다음이 성립한다.
아직 대상으로 잡히지 않은 원금
+ 살아 있는 회수 대상들의 금액 합
= 남은 원금
이 식이 있으면 어긋난 금액이 얼마인지가 아니라 어느 항에서 어긋났는지를 물을 수 있다. 조사의 방향이 정해진다.
대입하니 한 건이 통째로 남았다
실제 값을 넣어 봤다. 살아 있는 회수 대상이 둘이었고, 둘을 다 더하면 식이 7만 원 넘게 초과했다.
| 항 | 값 |
|---|---|
| 대상으로 안 잡힌 원금 | 39,593 |
| 살아 있는 대상 A | 71,105 |
| 살아 있는 대상 B | 9,454 |
| 합계 | 120,152 |
| 실제 남은 원금 | 49,047 |
여기서 A를 빼고 다시 계산하니 39,593 더하기 9,454가 정확히 49,047이었다. 식이 닫혔다.
A 하나만 없으면 모든 것이 맞으니, A는 있으면 안 되는 행이다.
A는 한 번도 회수된 적이 없었다
A의 이력을 보니 회수를 시도한 기록이 성공도 실패도 없었다. 만들어지기만 하고 아무 일도 일어나지 않은 채 2년 넘게 있었다. 미납 추적 기록은 하나 붙어 있었다. 회수된 적 없는 대상이 미납으로만 계속 잡히고 있었던 것이다. 그게 화면에 나온 숫자의 정체였다.
회수 시도가 없다는 사실이 중요했는데, 시도했다가 실패한 것이라면 미납으로 잡히는 게 맞기 때문이다. 시도 자체가 없는데 미납으로 잡혀 있다는 것은 이 대상이 살아 있으면 안 되는 상태로 남았다는 뜻이다.
같은 판매자의 다른 대상들과 비교해 봤다. 정상 대상은 만들어진 뒤 회수 시도 기록이 이어지거나, 취소됐으면 행 자체가 없었다. 둘 중 어디에도 속하지 않는 것은 이 하나뿐이었다.
없앤 것이 아니라 되돌리기만 했다
남은 질문은 A가 왜 살아 있느냐였다. 만들 때 원금에서 그 금액을 빼 두는데, 그 금액이 어느 시점에 원금으로 되돌아와 있었다. 즉 누군가 이 대상을 취소하는 작업을 했는데, 금액만 복구되고 행은 지워지지 않았다. 빠진 돈은 돌아왔는데 그 돈을 빼 갔다고 주장하는 행이 그대로 남은 것이다.
flowchart TD A["2024-04 · 대상 A 생성"] --> B["원금에서 71,105 차감"] B --> C["회수 시도 없음"] C --> D["어느 시점 · 정정 작업"] D --> E["원금에 71,105 복구"] D --> F["대상 A 행은 그대로 남음"] E --> G["복구된 금액이 새 대상으로 다시 차감됨"] F --> H["두 번 잡힌 상태"]
검산이 경로를 확정했다
여기까지는 추정이었고, 확정한 것은 그다음 계산이었다.
복구된 71,105원이 그대로 남아 있지 않았고, 그중 일부가 이후에 새 대상으로 다시 뽑혀 나갔다. 2024년 10월에 22,058원, 11월에 9,454원이 각각 새 대상이 됐고, 나머지 39,593원은 대상으로 잡히지 않은 채 남았다.
22,058 + 9,454 + 39,593 = 71,105
복구된 금액과 정확히 일치했다. 우연으로 맞을 수 있는 숫자가 아니어서, 이 시점에 추정이 경로가 됐다.
그리고 이 계산이 A가 유령이라는 것도 다시 증명했다. A가 빼 갔다고 하는 71,105는 이미 다른 곳에서 전부 소비됐다. 같은 돈이 두 군데에서 잡혀 있었던 것이다.
왜 2년이나 안 보였나
이 상태는 2024년부터 있었다. 그동안 아무 문제가 없었던 이유는 두 값이 각각은 그럴듯했기 때문이다. 미납 금액은 미납 금액대로 계산되고 남은 금액은 남은 금액대로 계산된다. 둘을 나란히 놓고 비교하는 화면이 생기기 전까지는 모순이 드러날 자리가 없었다.
화면이 문제를 만든 것이 아니라 화면이 문제를 보이게 했다. 모순을 검출하는 가장 싼 방법이 두 값을 같은 화면에 놓는 것이었다는 뜻이기도 하다.
수수료까지 따라 틀어져 있었다
미납 금액을 기준으로 매일 붙는 수수료가 있는데, 미납이 부풀어 있었으니 수수료도 그 위에서 계산되고 있었다.
그래서 고칠 범위가 행 하나가 아니었다. 잘못된 기준으로 쌓인 금액을 어디까지 되돌릴지, 이미 청구된 것은 어떻게 할지가 함께 정해져야 했다. 이건 개발 판단만으로 끝나지 않는다.
고치는 순서를 정했다
조치는 세 갈래였다. 유령 행을 정리하는 일, 그 행 때문에 부풀어 있던 미납을 되돌리는 일, 그 위에서 계산된 수수료를 정하는 일이다.
순서를 앞에서부터 잡았다. 유령 행을 먼저 정리해야 미납이 다시 계산됐을 때 맞는 값이 나온다. 반대로 미납부터 손대면 다음 계산에서 유령이 또 끼어들어 같은 값이 돌아온다.
고치기 전에 조사 내용을 그대로 적어 요청한 쪽에 보냈고, 정합식과 대입한 값, 검산, 복원한 시간선까지 넣었다. 요청은 값을 맞춰 달라는 것이었지만 실제로 필요한 결정은 수수료를 어디까지 되돌릴지였고, 그건 개발이 정할 수 없었다.
이렇게 하니 판단할 사람이 판단할 근거를 갖게 됐다. 숫자만 맞춰 놓고 조용히 끝냈으면 그 결정은 아무도 안 한 채 넘어갔을 것이다.
왜 행이 남았는지는 끝내 몰랐다
같은 형태가 더 있는지는 모른다. 이 판매자에게서 발견된 것이지 이 판매자만의 일이라고 볼 근거는 없다. 정합식이 있으니 전체를 훑는 검사를 만들 수는 있는데, 아직 안 만들었다.
정정 작업이 왜 행을 남겼는지도 확정하지 못했다. 2년 전 기록인 데다 당시 경로가 지금은 남아 있지 않아, 주체를 특정하지 못한 채 결과만 복원하는 데서 멈췄다.
식이 없었으면 어긋난 금액을 맞추는 것으로 끝났을 것이다. 식이 있으니 어느 항이 잘못됐는지 묻게 됐고, 검산이 맞아떨어지는 순간 추정이 사실이 됐다. 도메인에 항상 성립해야 하는 관계를 문장으로 적어 두는 일은 그 자체로 조사 도구가 된다.