STARRY PASS 6 — 두 열쇠가 서로를 기다린 밤
같은 돈을 동시에 고칠 때 생기는 두 사고를, 기다리는 열쇠와 바뀌는 도장으로 나누어 보는 STARRY 마감 이야기
공연이 끝난 STARRY 계산대에는 숫자표가 붙은 작은 금고 두 개와 봉인된 시험 봉투 여섯 장이 놓여 있었다. 히토리는 봉투를 한 줄로 세우다가 첫 장에 적힌 주의문을 소리 내어 읽었다.
“앞 봉투에서 남은 잔액을 다음 봉투로 가져가지 않는다. 시험을 바꿀 때마다 관련 표를 비우고, 그 시험이 요구한 시작값을 새로 만든다.”
“같은 밤에 이어지는 이야기지만 금고 속 데이터는 매번 새 무대라는 뜻이야.” 니지카가 말했다. “그래야 앞 실험의 흔적을 다음 결과로 착각하지 않지.”
키타가 첫 봉투를 열었다. 금고에는 10,000원이 든 카드 한 장이 있었다. 직원 두 명이 각각 1,000원을 빼기로 했다. 둘은 서로가 숫자를 읽을 때까지 기다렸다가, 함께 다음 동작으로 넘어갔다.
첫 직원은 10,000원을 읽었다. 둘째 직원도 10,000원을 읽었다. 첫 직원은 자신이 읽은 수에서 1,000원을 빼 9,000원을 적었다. 둘째 직원도 똑같이 9,000원을 적었다. 두 작업은 모두 완료표를 냈다.
히토리가 완료표 두 장을 겹쳐 들었다. “완료가 두 번이면 8,000원이어야 하는 것 아닌가요?”
료가 마지막 카드를 가리켰다. “완료 두 번. 마지막 잔액 9,000원. 변경 도장 0. 둘째 작업이 실패한 게 아니라, 두 작업이 같은 옛 숫자를 바탕으로 같은 새 숫자를 써서 변화 하나가 덮였어.”
이 실험에서는 평소 카드가 가진 변경 도장 보호를 일부러 건너뛰었다. 읽은 숫자가 아직 최신인지 묻지 않고, 시험 전용 통로에서 잔액만 바로 덮어썼다. 그래서 이 결과는 보호 장치 없는 사고 모양을 보여 주지만, 실제 서비스의 모든 저장 통로가 고장 났다는 뜻은 아니었다.

첫 실험에는 시간 울타리도 있었다. 각 직원의 작업 봉투는 최대 10초, 두 사람이 모두 읽었다는 신호를 기다리는 장벽도 최대 10초, 바깥에서 각 완료표를 받는 기다림은 최대 20초였다. 그 울타리는 시험이 영원히 매달리지 않게 하는 장치였다. 일부러 느린 금고나 시간 초과 사고를 만들어 검증한 것은 아니었다.
니지카는 결과표 아래에 세 줄을 적었다.
‘직접 본 것: 완료 2, 잔액 9,000, 도장 0.’
‘만들어 낸 조건: 두 직원이 같은 10,000원을 먼저 읽음.’
‘말하면 안 되는 것: 실제 서비스의 모든 동시 변경이 언제나 이 결과가 됨.’
히토리는 마지막 줄에 동그라미를 쳤다. “사고를 일부러 잘 보이게 만든 무대와 실제 영업 창구를 같은 것으로 말하면 안 되는군요.”
키타는 이번에는 마지막 잔액만 가리고 완료표 두 장만 히토리에게 보여 줬다. 히토리는 잠시 고민하다가 “둘 다 끝났다”까지만 답했다. 반대로 완료표를 가리고 9,000원만 보여 주자, 어느 작업이 실패했는지와 둘 다 같은 값을 썼는지를 구분할 수 없었다.
“그래서 동시 작업 영수증은 한 칸으로 끝나면 안 돼.” 니지카가 말했다. “몇 작업을 보냈는지, 각각 끝났는지, 마지막 상태가 무엇인지가 함께 있어야 해. 마지막 숫자 하나만 맞아도 중간 실패가 숨을 수 있고, 완료 수만 맞아도 지금처럼 변화가 사라질 수 있어.”
료는 ‘시작 10,000 / 요청 2장 / 요청당 1,000 / 완료 2장 / 마지막 9,000 / 도장 0’이라고 한 줄로 이어 썼다. 히토리는 그제야 머릿속 계산 8,000과 실제 관찰 9,000이 어느 칸에서 갈라졌는지 찾을 수 있었다.
두 번째 봉투를 열기 전에 키타는 금고를 다시 비우고 10,000원짜리 새 카드를 넣었다. 이번에도 직원 두 명이 각각 1,000원을 빼지만, 카드를 읽기 전에 열쇠부터 받아야 했다.
첫 직원이 열쇠를 쥐고 10,000원을 읽었다. 9,000원을 적고 변경 도장을 하나 올린 뒤 열쇠를 놓았다. 그동안 둘째 직원은 카드 앞에서 기다렸다. 열쇠를 받은 둘째 직원은 옛 10,000원이 아니라 최신 9,000원을 읽었고, 8,000원을 적었다.
완료표는 다시 두 장이었다. 이번 마지막 잔액은 8,000원이었다.
“첫 직원이 읽는 동안 둘째를 줄에 세웠기 때문에, 둘째가 최신 숫자에서 계산했어요.” 키타가 말했다.
“맞아. 이 무대에서는 카드 한 장을 읽을 때부터 작업이 끝날 때까지 열쇠를 쥔 효과가 있었어.” 니지카가 답했다. “다만 오늘 표가 직접 확인한 것은 완료 2와 잔액 8,000까지야. 마지막 변경 도장 숫자를 따로 채점하지 않았고, 누가 먼저 섰는지나 모두에게 공평한 줄인지도 재지 않았어.”
료는 첫 결과와 둘째 결과를 나란히 놓았다. 보호가 없는 통로는 10,000에서 두 번 계산했지만 9,000만 남았다. 열쇠 통로는 10,000에서 9,000, 다시 8,000으로 이어졌다. 같은 시작값이어도 읽는 순간을 보호하는지에 따라 마지막 값이 달라졌다.
“그러면 모든 카드에 열쇠를 걸면 끝인가요?” 히토리가 물었다.
니지카는 두 표 사이에 굵은 구분선을 그었다. “두 결과는 같은 금고에서 연달아 돈을 뺀 기록이 아니야. 각각 새로 10,000원을 넣고 시작한 독립 실험이야. 9,000원 결과에서 다시 두 번 빼서 8,000원이 된 게 아니라, 같은 시작선을 두 보호 방식에 따로 준 거지.”
히토리는 첫 표의 마지막 9,000원에서 둘째 표를 이어 그리던 화살표를 지웠다. 그 대신 두 표 위에 각각 ‘새 시작 10,000’을 적었다. 이야기의 장면은 이어져도 데이터의 시작 상태는 이어지지 않는다는 첫 주의문이 다시 필요해진 순간이었다.
료가 금고 두 개를 탁자 위에 올렸다. “카드가 두 장이면 열쇠도 두 개야. 잡는 순서가 제각각이면 새로운 사고가 생겨.”
세 번째 봉투에는 실제 실행표보다 먼저 사고를 상상해 보는 그림 카드가 들어 있었다. A 금고에서 B 금고로 돈을 옮기는 직원과, B에서 A로 옮기는 직원이 마주 보고 있었다.
키타가 A 열쇠를 먼저 집고 B 열쇠를 기다렸다. 히토리는 B 열쇠를 먼저 집고 A 열쇠를 기다리는 역할을 맡았다. 누구도 자기가 가진 열쇠를 놓지 않는다고 가정하자, 기다림 화살표가 A에서 B로, B에서 A로 이어져 동그라미를 만들었다.
“저희가 실제 금고를 이렇게 멈춰 세운 건 아니죠?” 히토리가 그림 카드를 조심스럽게 내려놓았다.
“응. 오늘 제공된 실행표는 일부러 이 사고를 일으켜 잡아내는 표가 아니야.” 니지카가 분명히 말했다. “이 그림은 왜 두 열쇠의 공통 순서가 필요한지 설명하는 위험 모형이야. 실행에서 실제로 사고가 한 번 났다가 자동으로 회복됐다고 쓰면 안 돼.”
니지카는 A와 B라는 이름 대신 각 카드의 고유 번호를 보자고 했다. 돈이 A에서 B로 가든 B에서 A로 가든, 항상 번호가 작은 카드의 열쇠를 먼저 받고 큰 카드의 열쇠를 나중에 받는 규칙이었다.
“B에서 A로 보내는 요청인데 A 번호가 더 작으면 A 열쇠부터 받는 거예요?” 키타가 물었다.
“그래. 열쇠 순서와 돈의 방향은 다른 칸이야.” 료가 답했다. “두 카드를 열어 온 뒤 원래 출발 번호와 도착 번호로 다시 찾아. 작은 카드가 언제나 출발 카드인 건 아니야.”
히토리는 ‘작은 카드 = 출발 카드’라고 썼다가 지웠다. 대신 두 줄을 만들었다. 첫 줄은 ‘열쇠: 작은 번호→큰 번호’. 둘째 줄은 ‘돈: 원래 출발 카드에서 빼고 원래 도착 카드에 더함’이었다.
실제 창구는 번호 두 개를 작은 순서로 정리해 금고 담당자에게 넘겼다. 금고 담당자의 조회표에도 번호 오름차순으로 카드를 가져오라는 줄이 있었다. 가져온 두 카드는 번호표로 다시 묶어, 원래 출발과 도착 역할을 복구했다. 카드가 정확히 두 장이 아니면 계산을 이어 가지 않았다.
“종이에 번호만 정렬하고 금고가 실제로 그 순서로 열쇠를 주지 않으면 부족하겠네요.” 히토리가 말했다.
“맞아. 그리고 다른 오래된 창구 하나가 손님이 말한 순서대로 열쇠를 잡아도 원이 다시 생길 수 있어.” 니지카가 답했다. “공통 순서는 관련된 모든 길이 함께 지켜야 의미가 있어.”
이제 실제 실행표를 펼칠 차례였다. 금고 표들을 다시 비운 뒤, 첫째 카드와 둘째 카드에 각각 10,000원을 넣었다. 두 카드의 주인은 모두 같은 손님 번호로 맞췄다.
키타는 A에서 B로 100원을 옮기는 요청 열 개를 만들었다. 표식은 ab-0부터 ab-9였다. 히토리는 B에서 A로 100원을 옮기는 요청 열 개를 만들었고, 표식은 ba-0부터 ba-9였다. 모두 합쳐 스무 작업이었다.
스무 직원은 각자 준비 깃발을 내린 뒤 출발 종을 기다렸다. 바깥 진행자는 최대 10초 동안 준비 깃발 스무 개가 모두 내려오는지 확인했다. 준비가 끝나자 종을 한 번 울렸다. 스무 작업이 완전히 같은 CPU 순간에 움직였다는 뜻은 아니지만, 한꺼번에 경쟁할 기회를 크게 만든 출발선이었다.
각 작업의 결과표는 최대 30초까지 기다려 하나씩 회수했다. 결과표를 받다가 예외가 나거나 시간이 넘으면 그 자리에서 실행은 실패였다. 마지막 합계가 우연히 맞더라도, 받지 못한 결과표를 조용히 버리고 성공으로 부르지 않았다.
히토리는 시험 삼아 결과표 하나를 뒤집어 숨긴 뒤 합계 저울만 내밀었다. 두 방향의 다른 변화가 서로 상쇄되면 총액은 여전히 20,000처럼 보일 수도 있었다. 니지카는 즉시 숨긴 표를 찾아냈다.
“총액 보존은 강한 확인이지만, 모든 요청의 완료를 대신하지는 않아. 반대로 스무 완료표가 있어도 장부 부호가 틀리거나 돈이 새로 생기면 안 되고.”
키타는 스무 칸짜리 체크판에 표를 하나씩 꽂았다. 마지막 한 장까지 들어온 뒤에만 잔액 저울과 장부 저울을 함께 열었다. 세 확인은 서로 경쟁하는 답이 아니라, 서로 다른 구멍을 막는 답이었다.
스무 장을 모두 받은 뒤에야 잔액을 새로 읽었다. 두 카드의 잔액을 더한 값은 20,000원이었다. 이동 장부에서 나간 돈은 음수, 들어온 돈은 양수로 모아 더했더니 0이었다.
“왕복 횟수와 금액이 같으니 각 카드도 10,000원이라고 결과표에 써도 되나요?” 키타가 물었다.
니지카가 고개를 저었다. “그렇게 계산될 것이라고 추론할 수는 있어도, 이 시험의 마지막 채점식은 각 카드가 반드시 10,000원인지 따로 비교하지 않아. 직접 채점한 값은 두 카드의 합 20,000과 장부 합 0이야. 증거표에는 assertion이 본 범위를 써야 해.”
료도 한 줄을 보탰다. “시험 이름에 멈춤이 없다는 말이 들어 있어도, 모든 계좌 조합과 모든 부하에서 영원히 멈춤이 없다는 증명은 아니야. 이 두 카드, 스무 작업, 이 시간 울타리, 이 실행에서 모두 끝났다는 관찰.”

실행이 끝나자 니지카는 같은 표를 새 표처럼 세 번 세지 못하게 했다. 첫 번째로 이 실행표를 꺼낸 뒤, 다음 봉투에서는 정답을 가리고 번호 순서의 이유를 다시 설명하고 같은 실행표를 돌렸다. 그다음 봉투에서는 결과를 다시 가리고, 모든 완료표의 제한 시간과 총액 보존을 다시 말한 뒤 또 같은 표를 돌렸다.
“세 번 실행했으니까 서로 다른 새 시험 세 개인가요?” 히토리가 물었다.
“아니.” 료가 답했다. “실행 노출은 세 번이지만 이름과 내용이 같은 시험 하나를 세 번 회수한 거야. 반복 학습 횟수와 고유 시험 수를 따로 세야 해.”
반복 봉투 사이에는 애플리케이션 금고와 분리된 SQL 연습장이 두 권 놓여 있었다. 첫 연습장은 거래 한 건마다 그 거래 계좌의 고객 이름을 옆에 붙이는 문제였다.
거래표에는 고객 번호가 직접 없었다. 거래표의 계좌 번호로 계좌표를 찾고, 계좌표의 고객 번호로 고객표를 찾아야 했다. 출발표를 고객표로 잡으면 거래가 없는 고객까지 섞이거나 한 행의 뜻이 바뀔 수 있었다. 그래서 첫 행의 뜻을 ‘거래 한 건’으로 고정하고, 연결 열쇠를 따라 두 번 이어 붙였다.
기본 연습장에는 거래표가 21행 있었다. 계좌와 고객 연결 열쇠가 모두 존재하는 자료이므로 결과도 거래 한 건당 한 행, 21행이었다. 이것은 실행 소스에 들어 있는 정답 파일을 복사한 결과가 아니라, 별도 연습장의 계약을 만족하는 예시와 씨앗 자료의 예상 수였다.
두 번째 연습장은 거래가 한 번도 없는 계좌를 찾는 문제였다. 이번에는 모든 계좌를 출발줄에 세웠다. 각 계좌 왼쪽은 반드시 남겨 두고, 오른쪽에 거래표를 붙였다. 붙을 거래가 없어 오른쪽 거래 번호가 빈 계좌만 골랐다.
기본 자료의 계좌는 여덟 개였다. 그중 103번 A-103과 104번 A-104만 거래표의 주 계좌 번호로 한 번도 등장하지 않았다. 결과는 계좌 한 건당 한 행, 정확히 두 행이었다. 계좌 하나에 거래가 여러 건 있더라도 ‘거래 없는 계좌’ 결과가 중복되지 않아야 했다.
히토리는 두 연습장의 방향을 바꾸어 적었다. 첫 문제는 거래를 잃지 않고 이름을 붙이는 일이므로 거래표에서 출발했다. 둘째 문제는 거래가 없는 계좌까지 남겨야 하므로 계좌표에서 출발했다.
“이어 붙인다는 말은 같아도, 무엇을 한 행으로 보존할지가 다르네요.”
“그게 시작표와 출력 한 행의 뜻을 먼저 적는 이유야.” 니지카가 말했다. “두 문제 모두 제공된 정본 답안 파일은 없어. 예시 문장과 네가 실제로 실행해 남긴 증거를 구분해야 하고.”
마지막 봉투에서는 열쇠 대신 카드의 변경 도장을 사용했다. 금고는 다시 비워졌고, 새 카드에는 잔액 10,000원과 도장 0이 찍혔다. 두 직원은 서로 독립된 작업 봉투를 열고 같은 카드를 읽었다.
둘은 모두 잔액 10,000원과 도장 0을 읽었다. 두 사람이 읽기를 마쳤다는 신호가 모일 때까지 다음 단계로 가지 않았다. 신호가 모이자 둘 다 1,000원을 빼 9,000원으로 만들고 저장을 시도했다.
첫 저장은 ‘현재 도장이 아직 0인가?’라는 조건을 만족했다. 금고는 잔액 9,000원을 저장하고 도장을 1로 바꿨다. 다른 저장은 옛 도장 0을 들고 왔지만, 금고의 현재 도장은 이미 1이었다. 금고는 두 번째 변화를 조용히 덮어쓰지 않고 충돌표를 돌려줬다.
결과 바구니에는 성공 1장과 예상된 도장 충돌 1장이 정확히 들어 있었다. 마지막 잔액은 9,000원, 마지막 도장은 1이었다. 예상 밖 기술 실패가 결과 바구니를 대신 차지했다면 이 순서 없는 두 장 비교를 통과할 수 없었다.
료는 결과 바구니를 세 칸으로 나눴다. 첫 칸은 저장 성공, 둘째 칸은 도장이 이미 바뀌었다는 예상 충돌, 셋째 칸은 그 밖의 고장이었다. 겉 포장지가 긴 오류가 와도 안쪽 원인을 따라가 도장 충돌인지 확인하고, 그것도 아니면 셋째 칸에 넣었다.
“둘 중 어느 직원이 성공했는지도 정해져 있나요?” 키타가 물었다.
“아니. 결과 비교는 순서를 따지지 않아.” 니지카가 답했다. “A가 성공하고 B가 충돌할 수도, 반대일 수도 있어. 직접 약속한 것은 승자의 이름이 아니라 성공 한 장과 예상 충돌 한 장이 함께 있다는 사실이야.”
“충돌한 쪽을 자동으로 다시 넣어서 둘 다 성공시키나요?” 히토리가 물었다.
“오늘 상자에는 그런 장치가 없어.” 니지카가 답했다. “이 표가 확인하는 것은 성공 하나, 예상 충돌 하나, 마지막 9,000과 도장 1이야. 다시 시도해서 8,000원이 되는지, 스무 명이나 천 명이 몰렸을 때 어떤지, 최대 몇 번 다시 보낼지는 범위 밖이야.”
각 작업 봉투의 최대 시간은 10초였다. 두 사람이 같은 옛 도장을 읽게 맞추는 신호도 최대 10초를 기다렸다. 바깥에서 각 결과표를 기다리는 시간은 최대 20초였다. 이것 역시 충돌 후 자동 재실행을 검증한 시간이 아니라 시험 정지를 막는 울타리였다.
료는 열쇠 방식과 도장 방식을 한 문장씩만 비교했다. “열쇠 방식은 먼저 기다리게 해서 최신 값을 차례로 읽는다. 도장 방식은 먼저 읽게 두되 저장할 때 옛 값이면 충돌을 드러낸다.”
키타가 뒤를 이었다. “둘 다 조용히 변화 하나가 사라지는 걸 막으려는 방법이지만, 오늘 시험만으로 어느 쪽이 모든 상황에서 더 빠르거나 더 좋은지는 정하지 못해요.”

세이카는 여섯 봉투를 모두 닫기 전에 계산표 한 장을 요구했다. 첫 봉투의 보호 없는 덮어쓰기 시험 1회, 둘째 봉투의 한 카드 열쇠 시험 1회, 반대 방향 스무 작업 시험 3회, 마지막 도장 충돌 시험 1회였다.
실행 노출은 모두 6회였다.
같은 반대 방향 시험을 세 번 실행했으므로 서로 다른 시험 함수는 4개였다. 그중 이번 주 자료에 새로 들어온 고유 시험은 반대 방향 스무 작업과 도장 충돌, 2개였다. 보호 없는 덮어쓰기와 한 카드 열쇠 시험 2개는 앞 주에 들어온 시험을 다시 꺼낸 것이었다.
여섯 봉투 모두 이미 제공된 시험을 가리고 설명한 뒤 다시 확인하는 범주였다. 새 시험 파일이 둘 생겼다는 것과 학습자가 그 정답을 처음 작성해 소유했다는 것은 다른 축이었다. 이 미리보기의 여섯 범위에서는 제공 자료 확인 표가 여섯 장이고, 학습자가 새로 작성했다고 인정된 표는 0장이었다.
니지카는 마지막 표를 둘로 나눴다. 왼쪽에는 직접 본 숫자를 적었다. 보호 없는 길의 완료 2·잔액 9,000·도장 0. 한 카드 열쇠 길의 완료 2·잔액 8,000. 반대 방향 건당 100원·스무 작업·합계 20,000·장부 합 0·각 결과표 30초 울타리. 변경 도장 길의 성공 1·예상 충돌 1·잔액 9,000·도장 1. SQL 연습장의 21행과 두 계좌 103·104.
오른쪽에는 아직 말할 수 없는 것을 적었다. 실제 기다림 원을 재현하고 자동으로 다시 시도했다는 주장. 모든 부하에서 멈춤이 없다는 주장. 각 계좌가 반드시 10,000원이라는 직접 채점. 모두에게 공평한 순서와 처리 속도. 충돌 뒤 재실행 성공. 스무 명·천 명의 도장 경쟁. SQL 문제의 제공 정본 답안.
히토리는 두 칸을 오래 바라보다가 말했다. “안전하다는 큰 단어보다, 어떤 시작값을 두고 몇 장의 결과표를 받았는지를 말하는 편이 오히려 이해하기 쉽네요.”
“그게 오늘의 열쇠야.” 니지카가 웃었다. “열쇠를 잡는 순서도, 우리가 말하는 근거의 순서도 뒤섞지 않는 것.”
료가 다음 상자를 계산대 아래에서 밀어냈다. 상자에는 ‘같은 카드에 출금 요청 20개’라고 적혀 있었다. 이번에는 반대 방향으로 돈을 되돌리는 짝이 없었다. 성공, 정상 거절, 예상 밖 오류를 하나도 잃지 않고 세어야 했다.
키타가 스무 장의 빈 결과표를 나누었다. “이번 밤에는 원을 만들 수 있는 순서를 정리했으니, 다음에는 몰려온 결과를 정확히 분류하네요.”
히토리는 마지막으로 작은 메모를 남겼다.
‘두 작업이 끝났다고 두 변화가 모두 남은 것은 아니다. 기다리게 할지, 옛 도장을 거절할지는 서로 다른 선택이다. 어느 쪽을 보더라도 시작값, 결과 수, 마지막 값, 그리고 시험이 직접 보지 않은 범위를 함께 적는다.’