STARRY PASS 8 — 같은 버튼을 쉰 번 누른 손님
손님이 결과를 못 보고 같은 버튼을 다시 눌러도, 돈은 한 번만 움직이게 만드는 접수함 이야기.
별이 거의 보이지 않던 밤, STARRY 계산대의 보내기 화면이 돌아가는 원 하나를 남긴 채 멈춰 있었다. 키타는 A 카드에서 B 카드로 1,000원을 옮기는 버튼을 눌렀다. 영수증은 안 나왔고, 로딩 표시만 돌았다.
“이미 보내진 건가요, 아니면 버튼이 씻힌 건가요?” 키타가 마우스 위에 손가락을 올린 채 물었다.
“손님은 그 둘을 구분할 수 없어.” 니지카가 답했다. “그러니 다시 누르는 게 자연스러워. 잘못은 손님이 아니라, 다시 와도 중복으로 돈을 옮기지 않는 장치가 없는 쪽이야.”
히토리는 화면을 보며 조용히 말했다. “저는 엘리베이터 버튼에 불이 들어왔는데도 다시 눌러요. 기계가 제 절실함을 더 잘 알아줄까 해서…….”
“기계는 절실함 대신 같은 요청을 두 장 받지.” 료가 간식 봉지를 접었다. “오늘은 두 장이 아니라 쉰 장으로 늘려 보자. 쉰 장이 왔을 때 실제 금고 일은 한 번이어야 해.”

니지카는 하얀 접수표 쉰 장을 계산대 위에 펼쳤다. 모두 같은 이체 요청이었지만, 번호 하나만 적히진 않았다. 접수표에는 업무 종류, 계산대가 확인한 손님, 손님이 정한 접수번호가 함께 들어갔다.
“접수번호만 보면 안 돼?” 키타가 물었다.
“다른 손님이 우연히 같은 번호를 쓸 수 있고, 다른 업무에서 같은 번호가 나올 수도 있어.” 니지카가 세 칸을 손가락으로 짚었다. “카드 이동 업무, 인증된 손님 customer-1, 접수번호 same-key. 이 세 가지가 한 묶음이야.”
히토리가 접수표 한 장의 손님 칸을 가리켰다. “손님이 직접 글자로 적은 이름을 믿나요?”
“아니. 계산대가 로그인을 통해 확인한 이름을 써야 해.” 니지카가 답했다. “보내는 카드의 실제 주인도 그 손님과 같은지 먼저 확인하고.”
료는 접수번호가 같다는 이유만으로 같은 주문이라고 믿으면 안 된다고 했다. 첫 접수표에는 A에서 B로 1,000원이라고 적히고, 두 번째 표에는 같은 번호와 A에서 B로 2,000원이라고 적힐 수 있었다. 그 둘을 같은 주문으로 취급하면, 2,000원을 보냈다고 믿는 손님에게 과거의 1,000원 영수증을 돌려줄 수 있었다.
“그럼 주문 글을 통째로 비교하면 되지 않을까요?” 키타가 물었다.
료는 같은 주문을 다른 모양으로 적은 두 장을 내밀었다. 한 장은 출발·도착·금액 순서였고, 다른 장은 금액·도착·출발 순서였다. 빈칸 넓이와 줄바꿈도 달랐다. 그러나 뜻은 모두 A에서 B로 1,000원이었다.
“화면 글의 모양을 비교하지 말고, 뜻이 되는 셋을 꺼내.” 료가 새 종이에 적었다. “출발 카드, 도착 카드, 금액. 항상 같은 순서, 같은 줄바꿈, 같은 숫자 표현으로 만들자.”
연습 값은 출발 10, 도착 20, 금액 3000이었다. 첫 줄에는 조리법 번호 v1을 적고, 다음 줄부터 출발 10, 도착 20, 금액 3000을 고정된 순서로 적었다. 마지막 줄 뒤에도 줄바꿈이 하나 있었다. 이 종이를 같은 글자 방식의 바이트로 바꿔 64자리 영어 소문자와 숫자 지문을 만들었다.
“출발을 10에서 11로, 도착을 20에서 21로, 금액을 3000에서 3001로 각각 바꾸면 지문은 다 달라져.” 료가 세 결과를 나란히 놓았다.
“지문은 암호화인가요?” 히토리가 물었다.
“아니. 원문을 복구해 보려는 표가 아니고, 원문을 비밀로 만드는 장치도 아니야. 같은 조리법에서 주문의 뜻이 같은지 비교하는 표식이야.”
니지카는 접수함 서랍을 열었다. 서랍 한 칸에는 접수표 한 장만 들어갈 수 있었다. 업무 종류·인증된 손님·접수번호 세 값이 모두 같은 두 번째 표를 넣으려 하면, 접수함 자체가 거절했다.
“먼저 서랍이 비었는지 보고, 비었으면 넣으면 안 되나요?” 키타가 물었다.
니지카가 표 두 장을 양손에 들었다. “두 계산대가 같은 순간에 봤다고 해 보자. 양쪽 모두 비었다고 볼 수 있어. 그 다음에는 두 장이 함께 들어가려고 하지.”
“보기와 넣기 사이에 틈이 있네.” 료가 말했다. “이런 일은 접수함에게 한 번에 시켜야 해. 새 표를 넣었다면 새 표의 번호를 돌려주고, 충돌했다면 아무 번호도 주지 않는 거야.”
접수함의 금속 규칙은 두 계산대가 아무리 같이 밀어도 결과 한 줄만 남기도록 되어 있었다. 첫 표의 지문이 a 소문자 예순네 개였고, 두 번째는 b 소문자 예순네 개였다. 하지만 세 칸이 같아서 두 번째 표는 거절됐고, 접수함의 행은 하나만 남았다.
접수함 규칙을 만들었으니, 이제 쉰 장을 동시에 밀어 넣을 차례였다. 쉰 명의 직원이 각자 같은 업무, customer-50, same-key, a 소문자 예순네 개의 지문을 든 채 출발선에 섰다. 쉰 명이 모두 준비했는지 최대 10초를 확인한 뒤, 니지카가 출발 깃발을 올렸다.
접수함에서 새 번호를 받은 표는 딱 한 장이었다. 그 표만 새 일의 주인이 됐다. 나머지 마흔아홉 장은 새 번호를 받지 못하고 이미 있는 접수라는 결과를 받았다. 모든 결과를 꺼낼 때는 각각 최대 30초를 기다리게 해 두었고, 마지막에 접수함을 세었을 때도 행은 하나였다.
히토리가 손을 번적 들었다. “쉰 명 중 한 명이 주인이면, 실제로 돈을 쉰 번 보낸 시험인 건가요?”
“아니.” 니지카가 즉시 선을 그었다. “지금 쉰 명이 경쟁한 건 접수함의 새 칸 주인 자리야. 실제 카드와 장부를 바꾸는 이체 전체를 쉰 번 부른 게 아니야.”
료는 정상 단어 중간에 따옴표가 든 수상한 손님 이름을 넣었다. 접수함은 그 글을 명령으로 오해하지 않았고, 이름 값 그대로 보관했다. 조회했을 때 그 이름과 다른 행은 0개였다.
키타는 “기존 접수”라는 표를 받은 마흔아홉 명에게 바로 성공 영수증을 나눠 주려 했다. 니지카가 그 손을 멈춰 세웠다.
“새 번호를 못 받았다는 것은 이미 접수된 표가 있다는 뜻일 뿐이야. 그 표가 지금 요청과 같은 뜻인지, 일을 다 끝냈는지까지는 아직 보지 않았어.”
료가 기존 행에서 세 종류의 정보를 꺼냈다. 처음 접수한 주문의 지문, 처리 중인지 완료인지 나타내는 표시, 완료되었을 때만 들어 있는 결과 숫자였다. “새 주인이 아닌 사람의 다음 일은 이 세 가지를 확인하는 거야. 접수표가 있다는 사실 하나를 성공과 같은 뜻으로 쓰면 안 돼.”
히토리가 고개를 끌덕였다. “그럼 주인 한 명을 뽑는 것은 시작일 뿐이군요. 나머지 사람이 무엇을 받을지는 지문과 상태와 결과를 본 뒤에야 정해져요.”

이제 접수함의 주인이 실제 이체를 시작했다. 출발 카드의 주인이 인증된 손님과 같은지 먼저 확인했다. 이를 붙잡을 두 카드는 번호가 작은 쪽부터 잠그었다. 출발 A에는 10,000원, 도착 B에는 5,000원이 있었고, 1,000원을 옮긴 뒤에는 9,000원과 6,000원이 되었다.
그것만으로 끝이 아니었다. 이동 사건을 적은 표 한 장, A에서 나간 1,000원 장부 한 줄, B로 들어온 1,000원 장부 한 줄이 만들어졌다. 마지막으로 접수함에 완료 도장을 찍고, 이동표 번호와 두 카드의 나중 잔액을 복사 영수증용으로 보관했다. 첫 성공의 응답 숫자 201도 함께 적었다.
키타가 큰 봉투 하나를 가져왔다. “접수표, 카드 두 장, 이동표, 장부 두 줄, 완료 도장을 모두 이 안에 넣어요?”
“바로 그거야.” 니지카가 봉투를 닫았다. “중간에 예상하지 못한 문제가 밖으로 튀어나오면, 봉투 안의 변경은 모두 함께 취소돼야 해. 접수만 완료인데 돈은 취소되거나, 돈은 옮겨졌는데 완료 결과가 없는 거짓 상태를 막는 거야.”
“하나의 일을 많은 일로 쪼개 보이게 하되, 성공과 취소의 경계는 하나로 묶는 군.” 료가 말했다.
히토리는 봉투의 첫 순서를 다시 보았다. 접수함에 새 표를 넣기 전에 출발 카드의 주인부터 확인하는 것이 눈에 띄었다.
“왜 접수표를 먼저 만들지 않아요? 보내기 요청이 왔으니 접수부터 하는 게 자연스러워 보이는데요.”
니지카가 거절된 손님의 가상 요청을 올려놓았다. “다른 사람의 카드를 보내는 카드라고 적은 요청이 먼저 접수함을 차지해 버리면, 실제 주인의 정상 요청이 같은 번호를 쓸 때 충돌할 수 있어. 출발 카드가 없는지, 그 카드의 주인이 로그인한 사람인지를 접수 전에 확인하는 이유야.”
료는 두 카드 번호를 작은 것부터 나열했다. 손님이 A에서 B로 보내든 B에서 A로 보내든, 잠그는 순서는 작은 번호 먼저였다. “손님이 쓴 방향을 그대로 잠금 순서로 쓰면 반대 방향 요청끼리 서로 다른 열쇠를 먼저 잡고 기다릴 수 있어. 방향이 달라도 순서는 하나로 맞추는 거야.”
완료 도장에도 조건이 있었다. 새 표를 받았을 때 받은 행 번호와 “처리 중” 표시가 여전히 있는 행 하나만 완료로 바꿀 수 있었다. 잘못된 번호나 이미 완료된 행을 다시 덮어쓰려고 하면, 바뀐 행의 수가 하나가 아니라는 이유로 작업을 실패시켰다.
“완료 도장이 있다는 것만 중요한 게 아니네요.” 키타가 말했다. “누가 받은 접수표인지와, 아직 처리 중인 표인지를 확인하고 딱 한 행만 바꿔야 하는 거군요.”
“그래.” 니지카가 답했다. “복사 영수증을 만들 수 있으려면 성공했다는 도장뿐 아니라, 첫 영수증을 다시 구성할 정확한 숫자가 필요해. 그래야 두 번째 요청이 금고 일을 다시 하지 않아도 결과를 보여 줄 수 있어.”
키타가 같은 customer-1, same-key, A, B, 1,000원으로 다시 보내기를 눌렀다. 이번에는 새 접수번호가 나오지 않았다. 계산대는 기존 행의 지문과 현재 주문의 지문을 비교했다. 둘은 같았고, 접수 상태는 완료였으며, 저장된 결과 세 값도 있었다.
“그럼 A에서 또 1,000원을 빼요?” 히토리가 물었다.
“아니.” 료가 완료 칸에서 과거의 숫자를 읽었다. “이동표 번호, A의 9,000원, B의 6,000원을 다시 보여 주고, 이것은 이미 끝난 결과를 다시 읽었다고 표시해.”
첫 응답에는 새로 만들었다는 201과 “재사용 아님”이, 두 번째에는 기존 결과를 보여 준다는 200과 “재사용 맞음”이 담겼다. 두 응답은 모두 성공이지만 뜻은 달랐다. 두 번째 응답은 새 이체가 아니었다.
다음에는 같은 접수번호로 2,000원을 보냈다. 접수함 주소는 같았지만 지문이 달랐다. 계산대는 과거 성공을 복사하지 않고 접수번호 충돌이라고 거절했다. 오류 종류는 “다른 주문과의 충돌”이었다. 이 오류는 손님에게는 409 응답으로 바뀌도록 준비되어 있었지만, 이 범위의 화면 시험이 직접 409를 눌러 본 것은 아니었다.
보내는 카드만 다르거나 받는 카드만 달라지는 경우도 추가 일을 만들지 않고 충돌했다. 기존 A와 B, 새로 만든 C 카드의 잔액은 각각 9,000원, 11,000원, 5,000원으로 남았다. 기존 이동표는 하나, 이체 장부는 두 줄, 접수함은 한 행이었다.
“같은 지문인데 완료 결과가 아직 없으면요?” 히토리가 물었다.
“완료인 척 영수증을 만들면 안 돼.” 니지카가 답했다. “현재 구현은 ‘처리 중’이라는 별도 충돌로 멈춰. 다른 주문 충돌과 같은 409 계열로 바꾸더라도, 오류 이름은 구분해야 해.”
니지카는 봉투가 정말 한꺼번에 취소되는지 보기 위해 두 곳에 고장 버튼을 달았다. 첫 번째는 접수함의 새 주인이 된 직후였다. 두 번째는 카드 잔액을 바꾸고 이동표와 장부를 쓴 직후였다. 둘 모두 예상하지 못한 프로그램 문제를 밖으로 던졌다.
첫 고장 뒤에는 접수 행 0, 이체 이동표 0, 이체 장부 0이었다. 두 번째 고장 뒤에도 같은 세 숫자가 모두 0이었다. 이 두 직접 시험은 카드 잔액을 별도로 언급하지는 않았지만, 더 넓은 누적 시험은 두 지점의 고장 모두에서 10,000원과 10,000원이 그대로임까지 확인했다.
“전원을 뽑아도 모든 순간이 반드시 같다고 주장할 수 있나요?” 히토리가 조심스럽게 물었다.
“안 돼.” 료가 답했다. “지금 넣은 건 동기식 데이터베이스 작업 안에서의 두 프로그램 예외야. 물리적 종료의 모든 순간, 네트워크 단절, 외부 결제 기계까지 자동으로 증명한 게 아니야.”
니지카는 오래된 처리 중 행을 무조건 지우고 다시 실행하는 방법에도 선을 그었다. 외부 기계에서는 돈이 이미 움직였지만 STARRY에 완료 도장만 못 왔을 수 있었다. 확인 없이 지우고 재실행하면 정말 두 번 옮겨질 위험이 있었다. 그 경우에는 외부 결과와 장부를 맞춰 보고 별도의 복구 규칙을 만들어야 했다.
료는 여러 시험 결과를 한 장의 큰 표로 모았다. 가장 먼저는 10,000원이 든 A와 10,000원이 든 B 사이에서 3,000원을 보냈다. 첫 결과는 새 처리, 두 번째는 결과 재사용이었다. 잔액은 7,000원과 13,000원, 계좌를 열 때 만들어진 두 줄까지 포함한 전체 장부는 4줄, 이체 두 줄의 부호 합은 0이었다.
다음에는 화면 글에서 칸 순서와 공백만 다르고 뜻은 같은 1,000원 요청을 두 번 보냈다. 숫자 세 개를 어설픈 규칙으로 뽑아내는 연습 도구를 써서, 둘은 같은 지문이 됐다. 잔액은 9,000원과 11,000원이고 추가 이체 효과는 하나도 없었다. 하지만 이 도구는 단순한 숫자 세 칸을 찾는 연습용이지, 모든 모양의 화면 글을 안전하게 읽는 일반 도구는 아니었다.
또 다른 시험은 같은 접수번호의 실제 이체 요청 스무 개를 같은 출발선에서 보냈다. 스무 결과 중 새 처리는 1개였고, A와 B는 9,000원과 11,000원, 계좌 열기까지 포함한 장부는 4줄이었다. 이는 실제 이체 전체를 스무 번 부른 시험이었다. 앞의 쉰 건 시험은 접수함만 경쟁시켰다는 차이를 다시 적었다.
반대 방향 이체도 스무 건을 실행했다. A에서 B로 100원씩 열 건, B에서 A로 100원씩 열 건이었다. 모두 끝난 뒤 두 카드는 각각 10,000원, 합은 20,000원, 계좌 열기 두 줄과 이체 스무 건의 두 줄씩을 합한 전체 장부는 42줄이었다. 각 결과에는 최대 20초 기다림이 붙었지만, 이 한 실험이 운영 환경에서 영원히 교착 없음이나 성능을 보장하지는 않았다.
마지막으로 손님 화면에서 같은 요청을 두 번 보냈다. 출발 카드는 10,000원, 도착 카드는 5,000원, 금액은 1,000원, 접수번호는 W12-REPLAY였다. 첫 응답의 숫자는 201이었다. 같은 본문을 다시 보낸 두 번째 응답은 200이었고, 본문에는 이미 끝난 결과를 재사용했다는 표시가 참으로 들어 있었다.
“이 화면 시험 하나로 다른 네 가지 화면 규칙까지 모두 증명했다고 하면 안 돼.” 니지카가 주의를 적었다. “직접 눌러 본 건 첫 201, 재사용 200, 재사용 표시 참이야. 다른 기능은 이 이야기 뒤의 범위야.”
“숫자가 작으면 증거가 약해 보여도, 본 것만 말하는 게 더 강한 문서야.” 료가 말했다.
히토리는 벽 하나를 여섯 칸으로 나눈 검사표를 보았다. 하루별 실행 묶음은 여섯 개였지만, 네 번째 묶음이 두 검사 대상을 함께 부르므로 최종 실행 대상은 일곱 개였다. 그 일곱 대상 안의 서로 다른 작은 검사를 세면 최종 성공 실행은 열여덟 개였다.
검사 묶음의 출처표를 기준으로 현재 작은 검사를 나누면, 이번 주 표시가 붙은 묶음 안에 일곱 개, 이전에서 발전해 이어진 묶음 안에 열한 개가 있었다. 이는 각 작은 검사가 최초로 쓰인 날을 뜻하지는 않았다. 발전해 이어진 묶음의 작은 검사들도 이름과 구성이 바뀌어 있었다. 시작할 때 의도적으로 실패하는 연습이 있는 칸은 네 곳이었다. 그 실패 실행까지 포함해 작은 검사가 물리적으로 실행된 횟수를 세면 스물여섯이었다. 열여덟과 스물여섯은 다른 의미였다.
“여섯, 일곱, 열여덟, 스물여섯…… 숫자가 왜 이렇게 많아요?” 히토리가 눈을 돌렸다.
“문을 연 횟수와, 문 안에서 확인한 항목 수와, 실패하는 예행연습까지 포함한 횟수가 다르기 때문이야.” 니지카가 각 숫자 옆에 이름을 붙였다. “최종에 문을 연 하루 명령은 여섯, 그 안의 검사 대상 클래스는 일곱이야. 실패 예행연습까지 더하면 하루 명령은 열, 검사 대상 클래스 횟수는 열둘, 작은 검사 실행은 스물여섯이 돼.”
코드 파일도 나누어 보았다. 이번 주의 최종 해답 파일은 네 개였다. 주문 지문, 접수함, 이체 중심, 화면 연결이었다. 그 네 파일을 확인하는 시험 파일 일곱 개와 화면 오류·응답·고장 도우미 네 개가 함께 열렸다. 그 외에 이전 주차에서 만든 접수함의 금속 규칙 파일 하나를 재검증했으므로, 이 범위에서 연 정본 파일은 총 열여섯 개였다. 새 해답 네 개와 정본 열여섯은 다른 집계였다.
키타가 스물여섯 번째 체크를 하고 펜을 내려놓았다. “이제 버튼을 다시 눌러도 무조건 다시 이체하는 게 아니라, 같은 접수인지와 같은 뜻인지, 완료 결과가 있는지를 순서대로 보는 거네요.”
“그리고 다른 뜻이면 성공인 척하지 않고 충돌이라고 말해.” 료가 덧붙였다.
“아직 끝나지 않았으면 끝났다고 말하지 않고.” 히토리가 따라 말했다. “자신 있게 다시 돈을 움직이는 것보다, 확실하지 않으면 멈추는 게 더 정직한 거네요.”
니지카는 그날의 마지막 규칙을 벽에 붙였다. 요청은 여러 번 도착할 수 있다. 같은 주문의 데이터베이스 효과는 제공된 동기식 범위에서 한 번이어야 한다. 그러나 이 한 줄은 모든 외부 기계와 모든 고장 순간, 모든 운영 규모의 영원한 보장을 뜻하지는 않았다.

세이카가 계산대 뒤에서 첫 201 영수증과 재사용 200 영수증을 바라보았다. 두 장은 모양이 달랐지만 같은 이동표 번호와 같은 잔액을 가리켰다. 새 이체는 한 번이었고, 두 번째 표는 과거의 결과를 정직하게 다시 보여 주었다.
“손님이 버튼을 쉰 번 누르지 않게 만들 수는 없어도,” 키타가 말했다. “쉰 번이 와도 누가 실제 일을 맡을지는 접수함이 한 번 결정할 수 있어요.”
“실제 이체 전체의 스무 요청에서도 새 효과는 하나였고.” 니지카가 덧붙였다. “하지만 쉰과 스무를 섞어 말하지는 말자.”
히토리는 조용히 고개를 끌덕였다. “같은 후회를 쉰 번 해도 인생이 한 번만 줄어드는 접수함이 있으면 좋겠네요.”
료가 고개를 저었다. “그건 이번 범위 밖.”
그래도 STARRY의 접수함은 제 자리에 서 있었다. 같은 요청을 억지로 새 일로 바꾸지 않고, 다른 요청을 과거의 성공으로 속이지 않으며, 아직 모르는 상태를 완료라고 장담하지 않는 접수함이었다.
이 문서의 공식 장면컷은 TV 애니메이션 《봇치 더 록!》 공식 사이트 자료입니다. ©하마지 아키·호분샤/애니플렉스. 접수함 사건과 개념도는 개인 학습용 비공식 구성이며, 재배포·상업 이용 권리를 뜻하지 않습니다.