STARRY PASS 5 — 사라진 두 번째 1,000원
두 사람이 같은 잔액을 동시에 만졌을 때, 둘 다 끝냈는데 왜 한 번만 반영되는지 추적하는 밤
공연을 마친 STARRY 계산대에는 닫힌 정산함 하나와 작은 시험 봉투 여섯 장이 놓여 있었다. 니지카는 봉투들을 날짜 대신 번호순으로 늘어놓고, 맨 앞에 굵은 글씨로 규칙을 적었다.
‘봉투를 바꿀 때마다 시험 무대를 새로 만든다. 앞 봉투의 결과를 다음 봉투의 시작값으로 쓰지 않는다.’
“그러면 오늘 이야기는 한 손님 돈이 여섯 번 이어서 움직이는 게 아니네요?” 히토리가 물었다.
“맞아. 같은 계산대를 쓰는 것처럼 보여도 매번 보관함을 비우고 그 시험이 요구한 카드와 기록을 다시 준비해.” 니지카가 답했다. “오늘은 새 기능을 마구 붙이는 날도 아니야. 이미 있던 약속을 가리고 설명한 뒤, 지금도 그대로 지켜지는지 다시 보는 날이지.”
키타가 첫 봉투를 열었다. 안에는 ‘돈을 옮기는 일은 반드시 공식 출입문으로 들어온다’는 짧은 문장이 있었다. 계산대 앞에는 멀쩡한 문 하나와 직원만 아는 지름길 하나가 그려져 있었다.
“공식 문으로 들어오면 문지기가 작업 전체에 큰 봉투를 씌워 줘요.” 키타가 말했다. “중간에 문제가 나면 봉투 안에서 바꾼 것을 한꺼번에 되돌릴 수 있게요.”
히토리는 시험표를 천천히 읽었다. 이 시험이 직접 확인하는 것은 딱 두 가지였다. 실제로 받은 이동 담당자가 문지기를 거치는 물건인지, 그리고 누구나 호출하는 이동 함수 자체에 ‘이 함수가 봉투의 경계’라는 표지가 붙어 있는지였다.
“그럼 실제로 돈을 옮겨 보고 성공과 취소까지 확인한 거예요?”
료가 고개를 저었다. “그건 다른 시험이 해. 이 첫 표는 문이 설치됐고 공개된 입구에 경계 표지가 있는지만 본다. 문이 있다는 사실과, 그 문을 지나 실제 모든 상황이 안전하다는 사실은 같은 말이 아니야.”

첫 봉투를 닫은 니지카는 두 번째 봉투에서 LOST-UPDATE라고 적힌 카드 한 장을 꺼냈다. 시작 잔액은 10,000원이었다. 카드 앞에는 두 개의 창구가 나란히 놓였다.
“두 창구에서 각각 1,000원씩 빼 달라는 요청이 동시에 왔다고 해 보자.”
히토리는 바로 8,000원을 적었다. 10,000원에서 1,000원을 두 번 빼면 8,000원이었다. 그런데 료는 두 직원의 읽기와 쓰기를 따로 적어 보라고 했다.
첫 번째 직원이 10,000원을 읽었다. 아직 첫 직원이 새 값을 저장하기 전에 두 번째 직원도 10,000원을 읽었다. 둘은 출발 신호가 떨어질 때까지 서로가 읽기를 끝냈다는 사실을 확인하고 기다렸다. 신호가 떨어지자 첫 번째 직원은 자기가 읽은 10,000원에서 1,000원을 뺀 9,000원을 저장했다. 두 번째 직원도 자기가 읽은 10,000원에서 1,000원을 뺀 9,000원을 저장했다.
완료표는 두 장이었다. 두 작업 모두 끝났다고 기록됐다. 하지만 마지막 카드에는 9,000원만 남았다.

“두 번째 빼기가 실패한 것도 아닌데 사라졌어요.” 히토리가 중얼거렸다.
“정확히는 두 번째 작업의 저장값이 첫 번째와 똑같아서, 변화 하나가 다른 변화 위를 덮은 거야.” 니지카가 말했다. “그래서 완료 횟수만 세면 사고를 못 봐. 마지막 값도 함께 봐야 해.”
이 실험에는 일부러 평소 보호 장치를 우회하는 시험 전용 메모 방식이 쓰였다. 직원은 저장하기 직전에 ‘내가 읽은 값이 아직 최신인지’를 확인하지 않고, 읽어 둔 숫자를 바탕으로 새 잔액을 그대로 덮어썼다. 마지막에는 카드의 변경 번호도 0인지 확인했다.
“그럼 우리가 실제 계산대에서 쓰는 모든 저장 방식이 고장 났다는 증거인가요?”
“아니.” 니지카가 빨간 선을 그었다. “이건 사고 모양을 확실히 재현하려고 만든 시험 전용 길이야. 실제 운영 경로의 변경 번호 보호가 깨졌다는 시험이 아니고, 세상의 모든 동시 작업이 늘 이렇게 된다는 뜻도 아니야.”
키타는 같은 시작 잔액 10,000원으로 시험 무대를 다시 만들었다. 이번에는 카드 한 장을 한 직원이 펼쳐 보는 동안 다른 직원이 기다리는 줄을 세웠다. 첫 직원이 10,000원을 읽고 9,000원을 저장한 뒤 카드를 놓았다. 그제야 둘째 직원이 9,000원을 읽고 8,000원을 저장했다.
완료표는 이번에도 두 장이었다. 최종 잔액은 8,000원이었다.

“줄을 세우면 두 변화가 모두 남네요.”
“오늘 준비한 두 작업에서는 그래.” 료가 말했다. “하지만 이 표가 모든 손님에게 공평한 순서를 약속하거나, 교착이 절대 없거나, 사람이 아주 많아도 빠르다고 증명한 건 아니야. 두 작업이 모두 끝났고 마지막이 8,000원이라는 데까지만 직접 봤어.”
두 실험의 기다림에도 울타리가 있었다. 각 독립 작업은 최대 10초짜리 봉투 안에서 수행됐고, 바깥에서 완료표를 기다릴 때는 작업마다 최대 20초만 기다렸다. 그것은 시험이 영원히 멈춰 있지 않게 하는 안전장치였다. 일부러 느린 보관함이나 실제 시간 초과를 만들어 동작을 검증한 것은 아니었다.
세 번째 봉투에는 FAIL-FROM 10,000원과 FAIL-TO 5,000원 카드가 들어 있었다. W7-FAIL이라는 이동표에는 1,000원이 적혀 있었다. 키타가 이동을 시작하자 보내는 카드와 받는 카드, 거래표와 장부표가 봉투 안에서 바뀌기 시작했다.
그리고 업무 값을 바꾼 바로 다음, 약속된 종이 한 번 울렸다. 종이 울리면 갑작스러운 중단이 일어나도록 미리 연결해 둔 시험이었다. 중단 문구는 ‘업무 변경 뒤 일부러 일으킨 중단’이라고 정확히 적혀 있었다.
히토리는 오래된 설명 카드 하나를 보다가 고개를 갸웃했다. 카드에는 고장 위치가 여러 곳인 것처럼 보이는 문장이 있었다. 니지카는 그 카드를 덮고 지금 실행되는 시험표를 다시 펼쳤다.
“오늘 근거는 이 시험표야. 여기에는 업무 변경 뒤 울리는 종 하나만 있어. 시작 직후의 별도 종이나 세 번째 고장 위치를 실제로 실행했다고 늘려 말하면 안 돼.”
중단이 난 뒤, 료는 작업 중 보던 화면을 그대로 믿지 않고 보관함을 새로 열었다. 보내는 카드는 10,000원, 받는 카드는 5,000원이었다. 이동이라고 적힌 거래표는 0장, 이동이라고 적힌 장부표도 0장이었다. 고장 장치가 실제로 불렸다는 표시도 켜져 있었다.
“그럼 보관함 전원이 나가거나, 너무 오래 기다리거나, 다른 종류의 오류가 나도 다 똑같다고 써도 될까요?”
“오늘 시험은 그 말을 허락하지 않아.” 니지카가 답했다. “직접 넣은 한 종류의 갑작스러운 중단과 한 위치만 확인했어. 실제 전원 중단, 느린 조회, 기다림 종료, 점검이 필요한 다른 오류는 별도 시험이 있어야 해.”
그 옆에는 독립된 장부 연습장이 한 권 놓여 있었다. 이번에는 거래표의 실패 이유 칸이 비어 있으면 화면에 NONE이라고 보여 주는 문제였다. 비어 있는 칸 자체를 저장된 글자 NONE으로 바꾸는 일이 아니라, 읽어 보여 줄 때만 빈칸 대신 NONE을 고르는 일이었다.
키타가 성공 거래 하나를 가리켰다. 그 행의 실패 이유는 비어 있었다. 히토리가 빈칸이면 NONE, 값이 있으면 원래 값을 고르는 표시식을 붙이자 화면에는 NONE이 나타났다. 실패 거래의 INSUFFICIENT_BALANCE 같은 실제 이유는 그대로 남았다.
“이 연습장은 방금 중단 시험이 자동으로 채점하나요?” 히토리가 물었다.
“아니. 같은 묶음에 들어 있을 뿐 별도 문제야.” 료가 말했다. “어느 표의 한 행을 읽는지, 결과 한 행이 무엇인지, 몇 행을 예상하는지 먼저 적고 직접 실행 결과를 남겨야 해. 제공된 정답 파일도 없으니 예시 문장과 자기 실행 증거를 구분해야 하고.”
네 번째 봉투에는 큰 투명 봉투와 작은 봉투가 포개져 있었다. 료가 빈 시험용 표를 만들고 먼저 큰 봉투 안에 OUTER_ROLLED_BACK이라는 표지를 넣었다. 그런 다음 큰 봉투를 잠시 멈추고, 별도 계산 담당자를 통해 새 작은 봉투를 열어 INNER_COMMITTED라는 표지를 넣었다. 작은 봉투는 그 자리에서 따로 닫아 확정했다.
큰 봉투로 돌아온 료는 ‘바깥 취소’라는 갑작스러운 중단을 일으켰다. 큰 봉투는 닫히지 못하고 안의 변경이 취소됐다. 마지막에 시험용 표를 새로 읽자 한 줄만 남아 있었다.
INNER_COMMITTED.
OUTER_ROLLED_BACK은 없었다.


“안쪽 기록을 따로 확정하면 더 안전한 거 아닌가요?” 키타가 물었다.
“필요한 경우는 있지만 자동으로 더 안전해지는 건 아니야.” 니지카가 말했다. “바깥 일이 실패했는데 안쪽 흔적만 남는 것이 요구사항일 때만 맞아. 둘이 반드시 함께 성공해야 하는 업무에 함부로 쓰면 오히려 부분 완료를 만들 수 있어.”
히토리는 두 담당자가 왜 서로 다른 물건으로 준비됐는지도 물었다. 료는 공식 문지기가 바깥 호출을 가로채 새 작은 봉투를 열어야 하기 때문이라고 설명했다. 한 물건이 자기 안의 다른 함수를 바로 부르면 그 문지기를 건너뛸 수 있어, 오늘 시험은 안쪽 담당자를 별도 물건으로 만들어 주입했다.
“그래도 이 한 줄로 감사 기록 설계가 옳다거나, 외부 결제까지 함께 안전하다고 말할 수는 없겠네요.”
“정확해. 확인한 것은 시험용 표에서 안쪽 한 줄만 남는다는 결과뿐이야.”
다섯 번째 봉투를 열자 새로운 고장 장치는 없었다. 세 번째 봉투에서 확인했던 똑같은 중단 시험표가 다시 들어 있었다. 정답과 이전 결과를 가리고 시간이 지난 뒤, 원인과 네 끝값을 다시 설명하고 같은 시험을 재실행하는 봉투였다.
히토리는 다시 적었다. FAIL-FROM 10,000원, FAIL-TO 5,000원, 이동 거래표 0장, 이동 장부표 0장. 고장 위치는 업무 변경 뒤 한 곳. 실제 시간 초과나 느린 조회는 확인하지 않음.
“같은 시험을 두 번 실행했으니 고유한 시험도 두 개로 세나요?”
“실행 횟수에는 두 번 들어가지만 서로 다른 시험 이름은 하나야.” 료가 답했다. “세 번째 봉투에서 한 번, 다섯 번째 봉투에서 한 번. 반복 회수라는 사실을 숫자에서 숨기면 안 돼.”
같은 봉투 옆에는 두 번째 독립 장부 연습장이 있었다. 2026년 7월 1일 하루의 거래만 고르는 문제였다. 씨앗 자료에는 정확히 세 시각이 들어 있었다. 7월 1일 00:00:00, 12:00:00, 23:59:59.999. 바로 다음 날인 7월 2일 00:00:00에도 다른 거래가 하나 있었다.
히토리는 하루의 마지막을 23:59:59라고 적으려다 멈췄다. 그렇게 쓰면 23:59:59.999를 놓칠 수 있었다. 대신 시작은 7월 1일 00:00:00 이상, 끝은 7월 2일 00:00:00 미만으로 정했다. 결과는 정확히 3행이었고, 다음 날 0시 행은 들어오지 않았다.
“하루 끝의 소수점 자릿수가 더 길어져도 다음 날 0시 미만이라는 규칙이면 놓치지 않겠어요.” 키타가 말했다.
“그래서 마지막 순간을 추측해 박아 두지 않는 거야.” 니지카가 답했다. “이 문제도 앱의 이동 시험과는 별도인 연습용 거래표에서 실행하고, 입력 한 행과 출력 한 행의 뜻, 예상 3행, 경계 반례를 함께 남겨야 해.”
마지막 봉투에는 코드보다 먼저 네 칸짜리 보관함 지도가 들어 있었다. 계좌 카드함, 업무 거래함, 장부함, 같은 요청을 중복 처리하지 않기 위한 요청함. 빈 보관소에 첫 설계도를 적용한 뒤, 이 네 종류의 표가 정확히 생기는지 보는 시험이었다.
히토리는 네 이름을 하나씩 읽었다. account, business_tx, ledger_entry, idempotency_request.
“열이나 규칙까지 전부 확인하나요?”
“이 시험문이 직접 비교하는 것은 표 이름 네 개야.” 료가 답했다. “순서는 상관없지만, 작업 도구가 따로 관리하는 이력표를 빼고 나면 이 네 표가 정확히 있어야 해. 각 열, 모든 연결 규칙, 빠른 찾기용 표식, 실행 중 봉투 동작까지 이 한 시험이 전부 확인하는 건 아니야.”
여섯 봉투를 모두 닫은 뒤 니지카는 마지막 계산을 시작했다. 첫 봉투의 시험은 1개였다. 두 번째 봉투에는 줄 없는 경우와 줄을 세운 경우, 2개가 있었다. 세 번째는 중단 시험 1개. 네 번째는 안쪽 별도 확정 시험 1개. 다섯 번째는 앞서 본 중단 시험 1개를 다시 실행했다. 마지막은 네 표 이름 시험 1개였다.
1 + 2 + 1 + 1 + 1 + 1 = 실행 노출 7회.
하지만 중단 시험 하나가 두 번 등장했으므로 서로 다른 고유 시험은 6개였다. 그중 이번 주에 처음 들어온 것은 줄 없는 경우, 줄을 세운 경우, 안쪽 별도 확정의 3개였다. 나머지 3개는 이전에 있던 공식 문, 중단 취소, 네 표 이름 시험을 다시 꺼낸 것이었다.

모든 봉투에는 ‘제공된 시험 결과를 다시 확인함’이라는 표지가 붙었다. 새 시험 세 개가 이번 주에 들어오기는 했지만, 학습자가 처음부터 쓴 정답으로 기록되는 항목은 0개였다. 여섯 봉투의 일곱 실행 결과가 모두 제공 자료 재확인 범주였다. 실행된 시험이 0개가 아니며, 실패·오류·건너뜀이 모두 0이고, 대상 파일의 지문까지 맞아야 초록 도장을 찍을 수 있었다.
히토리는 범위표의 왼쪽에 직접 본 것을 적었다. 공식 문지기와 공개 입구 표지. 10,000원에서 두 번 빼고도 9,000원만 남는 재현. 줄을 세워 8,000원이 되는 비교. 한 고장 지점 뒤 10,000원·5,000원·0·0. 바깥 중단 뒤 INNER_COMMITTED 한 줄. 네 핵심 표 이름. 그리고 별도 연습장의 빈 실패 이유를 NONE으로 보이기, 7월 1일 세 행 고르기.
오른쪽에는 아직 말할 수 없는 것을 적었다. 모든 동시 상황과 모든 격리 규칙, 공평함과 성능, 교착 없음, 실제 보관함 중단과 느린 조회, 모든 오류 종류, 자기 안에서 바로 부른 함수, 외부 시스템까지 묶인 원자성, 네 표의 모든 열과 제약.
“이렇게 적고 보니 초록 도장은 ‘어떤 느낌으로 안전하다’가 아니네요.” 히토리가 말했다. “어떤 시작값에서 어떤 일을 시켰고, 마지막에 무엇을 직접 읽었는지가 정해진 작은 약속이에요.”
니지카가 웃었다. “그리고 그 작은 약속 여섯 개를 섞지 않는 게 오늘 가장 큰 약속이지.”
료는 LOST-UPDATE 카드에 10,000→9,000을, 줄 세운 카드에 10,000→9,000→8,000을 다시 적었다. 키타는 두 장의 완료표 옆에 서로 다른 색의 동그라미를 그렸다.
공연장 불이 꺼질 무렵, 히토리는 마지막 문장을 남겼다.
‘완료가 두 번이라고 변화가 두 번 남은 것은 아니다. 마지막 상태를 함께 보아야 사라진 변화를 찾을 수 있다. 반대로 한 작업만 따로 확정하면 바깥 취소 뒤에도 그 흔적이 남을 수 있다. 어느 쪽도 무조건 좋은 주문은 아니다. 같은 봉투에 묶어야 할 것과 따로 닫아도 될 것을 먼저 정하고, 시험이 실제로 확인한 숫자까지만 말한다.’