11주차 미리보기
W11 미리보기 · 웹소설 본편

STARRY PASS 7 — 환불 창구에 선 스무 명

천 장의 업무표를 무한한 직원에게 떠넘기지 않고, 스무 번의 환불을 성공 열 장·정상 거절 열 장·기술 장애 영 장으로 끝까지 설명하는 이야기

STARRY의 낮 공연이 갑자기 취소된 날, 계산대 앞에는 손님 대신 번호표 스무 장이 놓였다. 실제 손님을 세워 두고 연습할 수는 없어서, 니지카가 같은 선불 카드에서 1,000원씩 돌려받으려는 스무 요청을 만든 것이다. 카드의 시작 잔액은 10,000원이었다.

키타는 번호표를 부채처럼 펼쳤다. “스무 명이 한꺼번에 오면 직원도 스무 명을 부르면 되죠?”

료가 그 옆에 더 두꺼운 묶음을 내려놓았다. “그럼 업무표가 천 장이면 직원도 천 명을 부를 거야?”

키타가 웃음을 멈췄다. 히토리는 천 명이 좁은 계단에 서 있는 광경을 떠올리다 조용히 벽 쪽으로 물러났다.

니지카는 탁자에 여섯 개의 봉투를 차례로 놓았다. 여섯 봉투에는 같은 시험이 다시 나오는 경우도 있었고, 한 봉투를 열면 네 가지 확인이 한꺼번에 실행되는 경우도 있었다. 그래서 봉투 수, 실제 확인 횟수, 서로 다른 확인의 수는 같지 않았다.

봉투는 여섯 번 열렸다. 그 안에서 확인 도장은 모두 아홉 번 찍혔다. 겹치는 이름을 하나로 모으면 서로 다른 확인은 여섯 개였다. 그중 다섯 개는 이번 연습에서 처음 들어온 것이고, 반대 방향 이체 확인 하나는 앞 연습에서 가져온 것이었다. 두 번째 봉투에서 일부러 틀린 창구를 먼저 돌리는 과정까지 세면 물리적인 실행은 하나 더 늘지만, 그것은 최종 통과 실행 수와 따로 적어야 했다.

selector 여섯 final-Green 실행 아홉 고유 테스트 여섯 신규 다섯의 구분
그림 한눈에: 여섯 봉투, 최종 통과 기준 아홉 확인, 서로 다른 여섯 확인과 이번에 새로 온 다섯 확인을 한 숫자로 뭉개지 않는다.
네 사람이 계산대에서 업무표와 창구 수를 나누어 세는 공식 장면
그림 한눈에: 네 사람이 같은 계산대에서 업무표 수와 실제로 일하는 창구 수를 따로 세며 연습을 시작한다.

니지카는 먼저 돈을 전혀 건드리지 않는 모형 업무표 천 장을 준비했다. 각 표는 출발 신호를 기다렸다가 아주 짧게 멈춘 뒤 성공표를 돌려주는 일이었다. 실제 카드나 금고를 쓰는 일이 아니었다.

첫 연습에서는 창구를 열 개만 열었다. 업무표는 천 장이었지만 동시에 일을 잡을 수 있는 직원은 최대 열 명이었다. 한 직원이 표 하나를 끝내면 줄의 다음 표를 집었다. 이어서 창구를 쉰 개, 이백 개로 바꿔 같은 천 장을 다시 돌렸다.

“업무표 천 장이 곧 직원 천 명이라는 뜻이 아니네요.” 히토리가 말했다. “저는 할 일이 천 개 생기면 자아도 천 개로 나뉘는 줄 알았는데…….”

“넌 자아 하나도 자주 멈추잖아.” 료가 답했다.

직원 수가 열 명인 실행에서 동시에 움직였다고 관찰한 최댓값은 적어도 한 명, 많아도 열 명이어야 했다. 쉰 명이면 한 명부터 쉰 명 사이, 이백 명이면 한 명부터 이백 명 사이였다. 반드시 모든 직원이 같은 순간 바쁘다고 채점하지는 않았다. 실제 운영체제가 어떤 순서로 깨우는지까지 통제할 수 없기 때문이다.

또 성공·업무 거절·기술 장애 세 바구니에 들어간 표의 합이 매 실행마다 천 장인지 확인했다. 기술 장애 바구니는 비어 있어야 했다. 다만 채점표가 성공 천 장과 업무 거절 영 장을 각각 직접 비교한 것은 아니었다. 이 모형 작업은 정상이라면 성공이라는 글자만 돌려주지만, 채점식이 직접 묻는 것은 세 바구니의 합 천 장, 기술 장애 영 장, 동시 활동의 상한이었다.

업무표 천 개와 고정 worker 열 쉰 이백의 상한
그림 한눈에: 업무표 1,000장은 고정 창구 10·50·200개가 차례로 처리하며, 관찰한 동시 활동은 각 창구 수를 넘지 않아야 한다.

“그러면 이백 창구가 제일 빠르다는 결론인가요?” 키타가 물었다.

니지카는 고개를 저었다. “그 속도를 비교하는 채점은 없어. 이건 창구 수의 상한과 결과표 회수 장치가 제대로 움직이는지 보는 연습이야. 실제 금고 연결 수도 모르고, 실제 환불도 하지 않았어.”

료는 보고서 첫 줄에 ‘모형 업무 천 장’이라고 크게 썼다. ‘실제 데이터베이스 환불 천 건’, ‘운영 처리량’, ‘최대 수용량’이라는 문장은 모두 지웠다.

천 장을 한꺼번에 겹치게 하려면 출발 장치가 필요했다. 직원들은 자기 차례가 되면 준비 깃발 하나를 내리고 닫힌 문 앞에서 기다렸다. 필요한 깃발이 모두 내려오면 진행자가 출발 종을 한 번 울렸다. 종이 울린 뒤에야 직원들이 일을 시작했다.

“각자 준비되는 대로 바로 하면 안 되나요?” 히토리가 물었다.

“그러면 첫 사람이 거의 다 끝난 뒤 마지막 사람이 시작할 수도 있어.” 니지카가 답했다. “우리가 확인하려는 겹침이 만들어지지 않았는데도 아무 사고가 없었다고 착각할 수 있지.”

료가 창구 두 개와 업무표 두 장을 놓았다. 준비 깃발은 일부러 세 개를 요구했다. 실제 직원 둘은 깃발을 하나씩 내려 두 개까지 만들고 출발 종을 기다렸다. 그러나 세 번째 깃발을 내릴 직원은 존재하지 않았다. 100밀리초짜리 모래시계가 끝나자 준비 실패표가 나왔다.

키타가 빈 계산대를 보았다. “아무 일도 안 일어났는데 조용한 성공으로 치면 안 되나요?”

“동시 연습을 시작하지 못한 거야.” 니지카가 말했다. “준비 시간 초과라는 말이 들어간 실패가 나와야 해. 기다리다 끝난 일을 성공으로 삼키면 가장 위험한 거짓 통과가 돼.”

실패가 나더라도 출발 종은 마지막 정리에서 내려갔다. 문 앞에 갇힌 직원이 남지 않게 한 뒤 창구를 닫았다. 정상 실행에서도, 중간에 문제가 난 실행에서도 정리 동작은 빠지지 않았다.

직원 둘로 준비 신호 셋을 기다려 ready timeout이 나는 반례
그림 한눈에: 직원 둘과 업무표 둘로 준비 깃발 셋을 기다리면 세 번째 신호는 올 수 없고, 100밀리초 뒤 준비 실패가 된다.

각 업무표를 끝내는 결과표에도 기다림 상한이 있었다. 바깥 진행자는 결과표 하나마다 최대 30초를 기다렸다. 첫 결과표부터 마지막 결과표까지 모두 합쳐 30초라는 뜻은 아니었다. 하나씩 차례로 꺼내며 각 표에 같은 상한을 적용했다. 모형 업무의 준비에는 최대 10초를 주었다.

“마지막 숫자가 맞으면 중간 결과표 몇 장은 못 받아도 괜찮지 않을까요?” 히토리가 물었다.

료가 세 바구니를 다시 가리켰다. “안 돼. 성공, 정상 거절, 기술 장애의 합이 제출한 업무표 수와 같아야 해. 결과를 잃어버린 채 마지막 숫자만 맞는 사고도 있거든.”

이제 진짜 카드가 등장했다. 시작 잔액은 10,000원, 요청은 스무 장, 요청마다 1,000원이었다. 모든 요청은 같은 손님과 같은 카드에 접근했지만, 요청표 이름은 0부터 19까지 서로 달랐다. 직원도 스무 명을 열어 모두 준비 깃발을 내린 뒤 출발 종을 함께 기다리게 했다.

니지카는 먼저 일부러 비어 있는 창구 규칙을 보여 줬다. 이 규칙은 요청 금액이 양수인지와 카드가 존재하는지, 카드 주인이 맞는지만 확인했다. 그 뒤에는 돈을 빼지도, 거래표를 만들지도, 장부를 적지도 않은 채 지금 보이는 잔액을 성공처럼 돌려줬다.

스무 요청은 모두 성공표를 들고 나왔다. 그런데 카드 잔액은 여전히 10,000원이었고 출금 장부도 영 줄이었다.

키타가 놀랐다. “실패가 아니라 성공이 너무 많아서 틀린 거네요.”

“맞아.” 니지카가 말했다. “채점표는 성공 열 장을 기대해. 첫 성공 수 검사에 우리가 붙여 둔 이름이 남으면서 의도한 실패가 드러나지. 아무 빨간 결과나 받는 게 아니라, 바로 그 계약이 깨졌다는 표시를 확인해야 해.”

히토리는 ‘성공 스무 장’을 좋은 소식 칸에 넣으려다가 멈췄다. 돈을 하나도 움직이지 않은 거짓 성공은 기술 장애만큼 위험했다. 채점표가 기대한 것은 스무 번 전부의 성공이 아니라, 가능한 열 번만 성공하고 나머지 열 번은 잔액 부족을 정확히 알리는 일이었다.

그들은 빈 규칙을 걷어 내고 완성된 창구 규칙을 직접 옮겨 적었다. 요청 금액이 양수인지 확인하고, 카드를 읽기 전에 그 카드의 행 열쇠를 잡았다. 카드가 있는지, 주인이 맞는지를 확인한 뒤 카드 안의 출금 규칙으로 잔액을 줄였다. 같은 작업 안에서 완료된 출금 거래표 한 장과 출금 장부 한 줄을 저장하고, 새 잔액을 돌려줬다. 이 모든 단계는 한 묶음으로 끝나거나 함께 되돌아가야 했다.

첫 성공은 10,000원을 9,000원으로 만들었다. 다음 성공은 열쇠를 받은 뒤 최신 9,000원을 읽어 8,000원으로 만들었다. 이런 식으로 열 번째 성공 뒤 잔액은 0이 되었다. 뒤에 열쇠를 받은 요청은 1,000원을 더 뺄 수 없었고, 잔액 부족이라는 정상 업무 거절을 돌려줬다.

스무 결과표를 전부 모으자 성공 열 장, 정상 거절 열 장, 기술 장애 영 장이었다. 카드 잔액은 0, 출금 종류의 장부는 열 줄이었다.

동시 출금 스무 건의 성공 열 업무 실패 열 기술 실패 영 잔액 영 원장 열
그림 한눈에: 행 열쇠 안에서 잔액 변경·거래표·장부를 한 묶음으로 처리해 성공 10, 잔액 부족 10, 기술 장애 0, 잔액 0, 출금 장부 10줄을 만든다.
니지카가 스무 요청의 다섯 결과를 화면으로 확인하는 공식 장면
그림 한눈에: 니지카가 성공 수만 보지 않고 정상 거절·기술 장애·마지막 잔액·장부 줄 수를 한 화면에서 함께 확인한다.

“어느 요청표 번호가 성공했는지도 정해져 있나요?” 키타가 물었다.

“아니.” 료가 답했다. “스무 직원이 경쟁하니까 0번부터 9번이 반드시 성공한다고 채점하지 않아. 성공이 열 장인지, 정상 거절이 열 장인지가 계약이야.”

업무 거절 바구니에는 아무 예외나 넣지 않았다. 잔액 부족이라는 정확한 업무 코드만 정상 거절이었다. 카드 없음이나 주인 불일치처럼 다른 업무 오류가 나오면 기술 실패 바구니로 갔다. 예상하지 못한 실행 오류도 기술 실패였다. 이렇게 나눠야 데이터베이스 연결 오류를 잔액 부족으로 위장하지 않았다.

준비 깃발은 최대 10초 기다렸고, 각 결과표는 최대 30초 기다렸다. 스무 결과표의 공통 30초 마감이 아니라 각 결과표를 차례로 받을 때의 상한이었다. 단 한 번의 실행에서 이 다섯 수가 맞았다는 사실은 중요했지만, 서른 번 반복 안정성이나 천 건 실제 금고 부하, 느린 쪽 응답 시간, 운영 처리량까지 증명하지는 않았다.

료는 장부 열 줄을 세고 펜을 내려놓았다. “열 줄이라는 확인만 있어. 각 줄의 금액과 부호, 거래표 열 장의 수, 요청표 이름의 유일성까지 이 채점이 모두 직접 묻는 건 아니야.”

“요청표 이름을 거래표 글자에 넣었으니 같은 이름이 다시 오면 한 번만 처리되나요?” 히토리가 물었다.

니지카는 다음 주에 쓸 번호표 상자를 꺼냈다가 다시 넣었다. “아직 아니야. 이름을 기록하는 것과, 같은 이름의 첫 요청만 주인이 되게 원자적으로 차지하는 건 달라. 이번 스무 장은 애초에 이름이 모두 다르고, 같은 이름을 재전송하는 시험이 없어.”

다음 봉투에는 방금 만든 스무 요청 확인이 다시 들어 있었다. 새 기능이나 새 시험이 아니었다. 정답을 가리고 준비 깃발, 출발 종, 세 결과 바구니와 마지막 두 숫자를 다시 설명한 뒤 같은 확인을 회수하는 봉투였다.

히토리가 새 도장을 꺼내려 하자 료가 막았다. “실행 노출은 한 번 늘어도 서로 다른 확인 수는 늘지 않아.”

니지카는 칠판에 ‘봉투 여섯’, ‘최종 확인 아홉’, ‘고유 확인 여섯’을 다시 적었다. 방금 스무 요청 확인은 두 봉투에서 각각 한 번씩 실행되지만 같은 한 가지 확인이었다.

그 옆에는 숫자 계산 연습이 있었다. 1부터 30까지의 값이 오름차순으로 놓였다. 가운데는 15와 16 사이였으므로 두 값을 더해 둘로 나눈 15.5였다. 느린 쪽 95퍼센트 지점은 서른에 0.95를 곱한 뒤 올림한 스물아홉 번째 값, 곧 29였다.

“95퍼센트면 28.5 아닌가요?” 키타가 물었다.

료가 자 위의 29를 짚었다. “여기서 정한 방법은 위치를 올림하는 방식이야. 다른 도구는 값을 사이에서 보간할 수도 있으니, 숫자만 쓰지 말고 계산 약속을 함께 적어야 해.”

일부터 삼십의 median 십오점오와 nearest rank p95 이십구
그림 한눈에: 정렬된 1부터 30에서 가운데 두 값 15·16의 평균은 15.5, 올림한 95퍼센트 위치는 29번째 값 29다.

비교표는 이름이 ‘전’과 ‘후’ 두 종류일 때만 받아들였다. 각 종류는 정확히 서른 줄이어야 했다. 전 서른 줄과 후 서른 줄, 모두 예순 줄이면 요약표를 만들었다. 한 줄이 빠져 쉰아홉 줄이 되거나, ‘다른 것’이라는 세 번째 이름이 하나라도 끼거나, 아예 빈 표가 오면 숫자를 그럴듯하게 계산하지 않고 입력 오류로 돌려보냈다.

before와 after 각 삼십 행만 허용하는 비교 입력 계약
그림 한눈에: 전 30줄+후 30줄만 통과하고, 총 59줄·세 번째 이름·빈 표는 비교 계약이 달라져 거절된다.

“그러면 후가 전보다 빨라졌다는 것도 확인한 건가요?” 히토리가 물었다.

“아니.” 니지카가 답했다. “표 모양과 계산법을 확인했을 뿐이야. 실제 시간 자료를 측정해 넣은 것도 아니고, 후가 더 작다고 비교하는 채점도 없어.”

이 숫자 연습 네 가지는 마지막 봉투에서 한꺼번에 다시 실행됐다. 창구 상한과 결과 천 장, 도달할 수 없는 준비 깃발의 실패, 1부터 30의 가운데와 느린 지점, 전후 표의 정확한 모양이었다. 그래서 마지막 봉투 하나가 확인 도장 네 개를 만들었다. 봉투 수와 실제 확인 횟수가 다른 가장 큰 이유였다.

한편 앞 연습에서 가져온 반대 방향 이체 확인도 다시 펼쳐졌다. 두 카드의 시작 잔액은 각각 10,000원이었다. 한 방향 열 건, 반대 방향 열 건, 모두 스무 작업이 각각 100원씩 옮겼다. 준비 깃발 스무 개와 출발 종 하나를 썼고, 각 결과표를 최대 30초씩 기다렸다.

스무 결과표가 모두 예외 없이 돌아온 뒤 두 카드의 잔액 합은 20,000원이었다. 이체 장부의 부호 있는 금액을 모두 더하면 0이었다.

이월 정렬 이체 스무 작업의 잔액 합 이만과 원장 합 영
그림 한눈에: 앞 연습에서 가져온 반대 방향 20작업은 건당 100원, 결과표마다 30초 상한, 잔액 합 20,000과 이체 장부 합 0을 확인한다.

키타가 두 카드 아래에 각각 10,000원을 적으려 하자 니지카가 멈췄다. “방향과 금액이 대칭이라 그렇게 끝날 거라고 예상할 수는 있어도, 이 확인의 마지막 채점은 카드별 10,000을 따로 묻지 않아. 합계 20,000과 장부 합 0이 직접 증거야.”

실제 교착을 일부러 일으키는 비교표도 없었다. 자동 재시도나 느린 기다림의 분포, 모든 부하에서의 안전도 확인하지 않았다. 앞 연습의 정렬된 열쇠 경로가 이 두 카드와 스무 작업에서 끝났다는 범위만 다시 확인했다.

계산대 아래에서는 애플리케이션 카드와 분리된 SQL 연습장 두 권이 나왔다. 첫 장에는 완료된 이체 하나와 그 아래의 장부 줄들이 그려져 있었다.

거래표 301번을 장부표에 이어 붙이자 같은 거래 번호 아래 두 줄이 나왔다. 101번 카드에서 300원이 나간 줄 하나, 102번 카드로 300원이 들어온 줄 하나였다. 거래 한 행이 장부 두 행과 이어졌으므로 결과가 두 행이 된 것이다.

하지만 모든 이체가 완전하다고 가정하면 안 됐다. 거래표 302번에는 101번 카드에서 200원이 나간 줄 하나만 있었고 들어온 줄은 일부러 빠져 있었다. 그래서 모든 이체 거래를 한꺼번에 붙이면 301번의 두 줄과 302번의 한 줄, 모두 세 줄이었다. 301번만 골라 ‘한 거래가 두 줄로 늘어나는 모양’을 볼 수는 있지만, 그것을 모든 거래가 완전하다는 증명으로 넓히지 않았다.

거래 삼백일의 두 원장 행과 거래 삼백이의 불완전 한 행
그림 한눈에: 거래 301은 나감·들어옴 두 장부 행으로 늘어나지만, 일부러 불완전한 거래 302는 나감 한 행만 있어 전체 이체를 붙이면 세 행이다.

이 연습장에는 복사할 정본 답안이 없었다. 시작 표, 한 행의 뜻, 예상 행 수와 불완전한 반례를 스스로 정해 실제 실행 기록으로 남겨야 했다. 301번을 좁혀 두 행을 보는 예시는 한 가지 학습 답안일 뿐, 제공된 유일 정답이 아니었다.

둘째 장은 손님마다 카드가 몇 장인지 세는 문제였다. 손님은 여섯 명, 카드는 여덟 장이었다. 1번·2번·3번 손님은 각각 카드 두 장, 4번은 한 장, 5번은 영 장, 6번은 한 장이었다.

“카드가 없는 5번 손님도 결과에 넣을까요?” 히토리가 물었다.

“문제가 바로 그 선택을 적으라고 해.” 니지카가 답했다. “모든 손님을 왼쪽에 남기고 카드를 붙이면 5번도 영 장으로 나와서 여섯 행이 돼. 카드가 있는 손님에서 시작하면 5번은 사라져 다섯 행이 되지.”

영 장을 포함하는 예시에서는 빈 연결을 하나로 잘못 세지 않도록 카드 번호를 셌다. 손님 행 전체를 세면 카드가 없어도 한 행이 남아 1로 보일 수 있었다.

고객 여섯 명의 계좌 수 이 이 이 일 영 일
그림 한눈에: 모든 고객을 남기는 선택에서는 카드 수가 2·2·2·1·0·1인 여섯 행이며, 내부 연결을 택하면 0계좌 고객이 빠진 다섯 행이다.

이 문제도 제공된 정본 답안 파일은 없었다. 영 장 손님을 포함할지 제외할지 가정을 먼저 적고, 그 선택과 맞는 시작 표·연결 방식·예상 행 수를 증거로 남겨야 했다. 니지카는 영 장을 포함하는 여섯 행 예시를 골랐지만, 그 선택을 숨긴 채 결과만 내놓지 않았다.

네 사람이 실행 사실과 SQL 가정을 구분해 검토하는 공식 장면
그림 한눈에: 네 사람이 실행에서 직접 본 값과 SQL 연습장의 가정·예시를 서로 다른 색으로 표시해 과장을 막는다.

마감표에는 세 종류의 증거가 나란히 놓였다. 새로 들어온 일반 창구 확인 네 가지, 새로 들어온 스무 출금 확인 한 가지, 앞 연습에서 가져온 반대 방향 이체 확인 한 가지였다. 서로 다른 확인은 여섯 개였고, 최종 통과 실행에서 실제 찍힌 확인 도장은 아홉 개였다.

일반 창구 확인은 모형 업무 천 장에서 직원 수 상한, 결과 수 보존, 준비 시간 초과 실패, 작은 숫자의 통계 계산과 전후 표 모양을 보장했다. 실제 금고 처리량이나 운영 속도는 보장하지 않았다.

스무 출금 확인은 한 번의 데이터베이스 실행에서 성공 10·잔액 부족 10·기술 장애 0·잔액 0·출금 장부 10줄을 확인했다. 서른 번 반복, 천 건 실제 부하, 지연 백분위, 같은 요청표의 중복 방지는 확인하지 않았다.

반대 방향 이체 확인은 스무 결과표 회수, 잔액 합 20,000, 이체 장부 합 0을 다시 확인했다. 카드별 잔액, 실제 교착 재현, 자동 재시도, 보편적인 안전은 확인하지 않았다.

히토리는 마지막 칸에 ‘모든 것이 안전하다’라고 쓰려다 멈췄다. 대신 ‘어떤 무대에서 무엇을 직접 봤는가’를 숫자와 함께 적었다.

니지카는 확인표를 한 번 더 뒤집었다. 앞면에는 통과한 숫자가 있었고, 뒷면에는 그 숫자를 만들지 않은 조건이 적혀 있었다. 모형 직원이 천 장을 처리한 컴퓨터와 실제 스무 출금이 돈을 움직인 금고는 같은 상자가 아니었다. 작은 통계 자의 서른 값도 실제 응답 시간을 서른 번 잰 기록이 아니었다. 같은 ‘천’과 ‘서른’이 보여도 무엇을 세었는지부터 확인해야 했다.

키타는 숫자 옆에 단위를 붙였다. 천은 모형 task, 서른은 독립 벡터와 variant별 sample 행, 스무는 실제 출금 요청과 이월 이체 작업이었다. 단위가 붙자 서로 빌려 쓸 수 없는 증거가 눈에 보였다. 히토리는 마지막으로 ‘한 번 관찰’과 ‘언제나 보장’ 사이에도 굵은 선을 그었다.

“결과가 많아질수록 짧게 말하기가 어려워져요.” 히토리가 말했다.

“그래서 숫자를 버리는 게 아니라 상자를 나누는 거야.” 니지카가 답했다. “모형 창구, 실제 출금, 이월 이체, SQL 예시. 각 상자의 경계를 붙이면 쉬운 설명도 정확할 수 있어.”

료는 요청표 이름이 적힌 빈 상자를 계산대 가운데 놓았다. “다음에는 같은 이름의 요청이 쉰 번 도착해도 돈은 한 번만 움직여야 해.”

키타가 상자에 ‘첫 요청의 주인’이라는 표를 붙였다. 히토리는 스무 명의 경쟁보다 같은 의도 쉰 번이 더 무섭다고 생각했지만, 이번에는 도망가지 않고 다음 연습의 첫 줄을 적었다.

“기록된 이름과 한 번만 처리한다는 약속은 다르다.”

그 문장 아래에는 성공 열 장, 정상 거절 열 장, 기술 장애 영 장, 잔액 영 원, 장부 열 줄이 가지런히 놓여 있었다.

이미지 출처와 이용 안내: 공식 장면컷은 TV 애니메이션 《봇치 더 록!》 공식 사이트 자료입니다. ©はまじあき/芳文社・アニプレックス. 이 문서는 개인 학습용 비공식 구성으로 원작의 공식 설정과 별개이며, 개념 그림은 실제 W11 소스와 SQL workbook seed를 바탕으로 새로 만든 교육용 SVG입니다.

W11 미리보기 · 히토리 질문 노트

히토리의 질문 노트 — W11 동시성 시험의 경계

본편의 쉬운 창구 비유를 실제 클래스·메서드·SQL 이름으로 연결합니다. 각 답은 직접 assertion한 값, 코드에서 따라오는 사실, 이번 월~토 범위가 보장하지 않는 것을 분리합니다.

1. 이번 미리보기의 정확한 원문 범위는 어디까지인가요?

원문 PDF 309쪽에서 W11 | 최초 계획 2026.09.21-09.27 | 동시성 검증 harness가 시작되는 지점부터, 353쪽의 일 모듈 | W11 동시성 누적 Gate 표제가 나오기 직전까지입니다. 353쪽 상단의 여섯 번째 모듈 최종 Gate는 포함하지만 같은 쪽에서 일 모듈이 시작된 뒤와 354~361쪽의 누적·주간·면접 확장은 포함하지 않습니다.

월~토 manifest day 1~6만 따릅니다. 기존 초안에 있던 실제 30회 반복 실행, structured logging, logging overhead 비교, 운영 TPS 보고, 거래별 pair 무결성 일반화는 이 범위의 실행 소스가 아니므로 본문 보장으로 가져오지 않습니다.

실제 기술 이름W11 PDF p309 W11 heading through p353 before Sunday-module heading, manifest days 1–6 only

2. selector 여섯 번과 실제 @Test 실행 아홉 번은 왜 다른가요?

day 1~6에는 selector 호출 면이 여섯 개입니다. 그러나 마지막 selector는 ConcurrencyHarnessTest 클래스 전체라 그 안의 @Test 네 개를 실행합니다. 최종 Green selector를 각 모듈에서 한 번씩 실행한다고 세면 1 + 1 + 1 + 1 + 1 + 4 = 9개의 테스트 메서드 실행입니다.

day 2의 의도적 Red와 수정 뒤 Green을 물리 실행 횟수로 모두 세면 10회가 되지만, 그 수는 final-Green day-run 9회와 분리해야 합니다. selector 호출 6, final-Green 메서드 실행 9, Red까지 포함한 물리 실행 10은 서로 다른 집계입니다.

실제 기술 이름six day-selector invocations, nine final-Green @Test executions, ten physical executions including W11D2 Red

selector 여섯 final-Green 실행 아홉 고유 테스트 여섯 신규 다섯의 구분
그림 한눈에: selector 호출·메서드 실행·고유 메서드·신규 메서드 수를 한 칸에 섞지 않습니다.

3. 서로 다른 테스트 여섯 개 중 이번 W11에 새로 들어온 것은 몇 개인가요?

고유 @Test는 여섯 개입니다. ConcurrencyHarnessTest의 네 메서드와 ConcurrentWithdraw20IT의 한 메서드, SortedLockTransferIT의 한 메서드입니다. 앞의 다섯 메서드는 W11D1·W11D2에서 새로 들어왔고, 정렬 이체 메서드 하나는 W10D3에서 이어 왔습니다.

정본 소스도 분리합니다. W11 신규 정본은 ConcurrencyHarnessTest.java, ConcurrentWithdraw20IT.java, day-2 solution/WithdrawalService.java 세 파일입니다. 이월 정본은 W10D3 SortedLockTransferIT.java 한 파일입니다. selector body는 테스트 클래스 세 파일이고, WithdrawalService는 selector가 호출하는 신규 production solution입니다.

실제 기술 이름unique @Test 6 = W11-new 5 + carried W10 1; canonical final sources 4 = W11-new 3 + carried 1

4. 업무표 1,000장과 worker 10·50·200은 어떤 관계인가요?

fixed_pool_bounds_observed_activity_and_accounts_for_all_tasks는 workers를 10, 50, 200으로 바꾸며 매번 run(workers, 1_000, Math.min(workers, 1_000), Duration.ofSeconds(10))을 호출합니다. Executors.newFixedThreadPool(workers)이 만든 제한된 worker들이 1,000 task를 차례로 가져갑니다.

따라서 tasks=1,000은 thread=1,000을 뜻하지 않습니다. 각 task는 start latch 뒤 active를 올리고 LockSupport.parkNanos(100_000L)SUCCESS를 반환하며 finally에서 active를 내립니다. 이 100,000나노초는 0.1밀리초짜리 모형 작업이지 실제 출금 SQL 시간이 아닙니다.

실제 기술 이름ConcurrencyHarnessTest.fixed_pool_bounds_observed_activity_and_accounts_for_all_tasks, fixed thread pool, tasks versus workers

업무표 천 개와 고정 worker 열 쉰 이백의 상한
그림 한눈에: 1,000개 task를 10·50·200개의 고정 worker가 처리하며 worker 수가 동시 활동 상한입니다.

5. observedMaxActive는 workers와 정확히 같아야 하나요?

아닙니다. 직접 assertion은 observedMaxActive가 1 이상 workers 이하라는 범위입니다. task가 매우 짧고 스케줄링이 달라질 수 있으므로 모든 worker가 같은 순간 active였다고 요구하지 않습니다.

이 테스트가 증명하는 것은 관찰한 동시 활동이 고정 pool 크기를 넘지 않았다는 점입니다. CPU core 활용률, 실제 DB connection 수, 모든 worker의 동시 포화, worker 수가 커질수록 처리량이 좋아진다는 결론은 assertion하지 않습니다.

실제 기술 이름RunResult.observedMaxActive, AssertJ isBetween(1, workers), bounded concurrency observation

6. 일반 harness의 세 결과 bucket은 무엇을 직접 assertion하나요?

RunResultsuccess, businessFailure, technicalFailure, observedMaxActive 네 값을 가집니다. 직접 assertion은 세 결과 bucket의 합이 1,000이고 technicalFailure가 0이라는 것입니다. 현재 모형 task는 문자열 SUCCESS를 반환하므로 정상 코드 흐름에서는 success=1,000·business=0이 되지만, 테스트 문장은 두 값을 각각 직접 비교하지 않습니다.

future.get(30, TimeUnit.SECONDS)SUCCESS 이외 문자열을 받으면 business bucket, 예외를 받으면 technical bucket으로 셉니다. 실제 BusinessException을 분류하는 로직은 이 일반 harness가 아니라 20건 출금 통합 테스트에 있습니다.

실제 기술 이름RunResult(success, businessFailure, technicalFailure, observedMaxActive), completeness invariant, technical-failure zero assertion

7. Future.get(30초)는 전체 실행의 공통 30초 deadline인가요?

아닙니다. for (Future<String> future : futures) 안에서 각 Future에 future.get(30, TimeUnit.SECONDS)를 차례로 호출합니다. 코드가 하나의 공통 deadline을 계산해 남은 시간을 줄이는 구조가 아니므로 “모든 1,000 task가 전체 30초 안에 끝난다”는 assertion이 아닙니다.

예외나 timeout은 이 일반 harness 안에서 technical bucket으로 들어가고 결과 합에 포함됩니다. 하지만 task 본체가 0.1밀리초 모형이라는 사실과 함께 읽어야 하며, 실제 데이터베이스 SLA나 end-to-end latency 보장이 아닙니다.

실제 기술 이름per-Future Future.get(30, TimeUnit.SECONDS), no global deadline, synthetic task envelope

8. ready latch와 start latch는 각각 무엇을 하나요?

각 task는 먼저 ready.countDown()으로 worker에 올라왔음을 알린 뒤 start.await()에서 멈춥니다. 시험 thread는 ready.await(readyTimeout...)이 성공한 뒤 start.countDown()으로 기다리는 task들을 풀어 줍니다. 준비와 출발을 다른 latch로 나눠 경쟁할 기회를 만듭니다.

이 장치는 test process 안의 실행 순서를 조정할 뿐 데이터베이스 row lock이 아닙니다. 모든 task가 정확히 같은 CPU 나노초에 실행됐다는 증명도 아닙니다. 고정 pool에서는 readyTarget이 동시에 worker에 올라올 수 있는 수를 넘지 않도록 해야 합니다.

실제 기술 이름two-latch concurrency barrier, CountDownLatch ready, CountDownLatch start, test coordination not DB locking

9. workers=2, tasks=2, readyTarget=3이면 왜 반드시 실패하나요?

ready_timeout_is_a_failure_and_never_a_green_runrun(2, 2, 3, Duration.ofMillis(100))을 호출합니다. task가 두 개뿐이어서 ready.countDown()도 최대 두 번입니다. 초기값 3인 latch는 1에서 멈추고 100밀리초 뒤 IllegalStateException이 납니다.

직접 assertion은 예외 형식이 IllegalStateException이고 메시지에 ready timeout이 포함된다는 것입니다. finally가 start.countDown()pool.shutdownNow()를 수행하므로 start에서 기다리는 worker도 정리합니다. 이것은 DB lock timeout이나 Future timeout 테스트가 아닙니다.

실제 기술 이름ConcurrencyHarnessTest.ready_timeout_is_a_failure_and_never_a_green_run, unreachable ready target, fail-closed setup

직원 둘로 준비 신호 셋을 기다려 ready timeout이 나는 반례
그림 한눈에: 만들 수 없는 세 번째 ready 신호를 기다린 실행은 조용한 Green이 아니라 정확한 준비 실패입니다.

10. day-2 starter는 왜 의도한 Red가 되나요?

starter WithdrawalService.withdraw는 amount 양수, account 존재, owner 일치만 확인한 뒤 account.getBalance()를 반환합니다. 잔액을 줄이지 않고 business transaction과 ledger도 저장하지 않습니다. fixture의 스무 호출은 모두 올바른 actor와 account를 쓰므로 코드 흐름상 성공 문자열 스무 개를 만들고 balance 10,000·WITHDRAWAL ledger 0행을 남깁니다.

테스트의 첫 성공 수 assertion은 10개를 기대하므로 이 지점에서 W11D2_RED_EXPECTED_ATOMIC_WITHDRAWAL 설명이 붙은 실패가 납니다. 임의의 컴파일 오류나 다른 assertion 실패가 아니라 이 계약 marker를 확인한 뒤 solution을 직접 입력해야 합니다.

실제 기술 이름W11D2 starter WithdrawalService, W11D2_RED_EXPECTED_ATOMIC_WITHDRAWAL, intentional learner-authored Red

11. 20건 동시 출금 fixture의 시작값과 시간 경계는 무엇인가요?

@BeforeEachidempotency_request, ledger_entry, business_tx, account 네 표를 TRUNCATE ... RESTART IDENTITY CASCADE로 비우고 owner customer-1, account number WITHDRAW-20, balance 10,000인 계좌를 엽니다. tasks=20, fixed pool=20, ready latch=20이며 각 요청 금액은 1,000입니다.

requestId는 withdraw-0부터 withdraw-19까지 서로 다릅니다. ready는 최대 10초, 각 Future는 최대 30초 기다립니다. 이 시간 역시 전체 스무 요청의 공통 30초 deadline이 아니라 Future별 retrieval 상한입니다.

실제 기술 이름ConcurrentWithdraw20IT.setUp, tasks 20, amount 1,000, balance 10,000, ready 10s, per-Future 30s

12. success·business failure·technical failure는 어떻게 나뉘나요?

withdrawals.withdraw(...)가 정상 반환하면 SUCCESS입니다. BusinessException 중 code가 정확히 INSUFFICIENT_BALANCE이면 BUSINESS_FAILURE, 다른 business code이면 TECHNICAL_FAILURE입니다. 그 밖의 RuntimeException도 technical bucket입니다.

따라서 account 없음이나 access denied를 잔액 부족처럼 정상 거절로 세지 않습니다. 반대로 예상 가능한 잔액 부족을 시스템 장애로 세지도 않습니다. 결과 분리는 숫자 맞추기뿐 아니라 장애 대응 대상을 나누는 계약입니다.

실제 기술 이름BusinessException, ErrorCode.INSUFFICIENT_BALANCE, outcome classification

13. Green에서 직접 assertion하는 다섯 결과는 무엇인가요?

직접 assertion은 SUCCESS 10개, BUSINESS_FAILURE 10개, TECHNICAL_FAILURE 0개, 최종 balance 0, entry_type='WITHDRAWAL'인 ledger row 10개입니다. 어느 sequence가 성공할지는 assertion하지 않습니다.

business transaction count, ledger 각 행의 amount·signed amount, 열 requestId의 유일성, 같은 requestId 재전송, 30회 반복 안정성은 이 테스트가 직접 확인하지 않습니다. 다섯 결과를 넘어서 보장을 넓히지 않아야 합니다.

실제 기술 이름ConcurrentWithdraw20IT.twentyConcurrentWithdrawalsHaveTenSuccessTenBusinessFailureAndNoTechnicalFailure, 10/10/0, balance 0, ledger 10

동시 출금 스무 건의 성공 열 업무 실패 열 기술 실패 영 잔액 영 원장 열
그림 한눈에: 스무 요청의 세 bucket과 최종 balance·ledger가 같은 사건을 설명해야 Green입니다.

14. solution WithdrawalService는 어떤 순서로 한 출금을 처리하나요?

amount가 양수인지 확인하고 accounts.findOneForUpdate(accountId)로 row lock을 포함한 계좌를 읽습니다. account 없음과 owner 불일치를 확인한 뒤 account.withdraw(amount)로 domain 잔액 규칙을 적용합니다. 이어 현재 시각을 만들고 completed withdrawal BusinessTransactionLedgerEntry.withdrawal을 저장한 뒤 새 balance를 반환합니다.

method 전체에는 @Transactional이 붙어 있습니다. 잔액 변경·업무 거래·원장이 같은 transaction 안에서 성공하거나 함께 rollback되는 경로입니다. row lock은 transaction이 끝날 때까지 의미가 있습니다.

실제 기술 이름W11D2 solution WithdrawalService.withdraw, @Transactional, findOneForUpdate, transaction-and-ledger atomicity

15. 왜 findOneForUpdate가 성공 10·업무 실패 10을 만드는 데 중요한가요?

경쟁 요청은 같은 account row를 읽기 전에 pessimistic lock을 요청합니다. 먼저 잠금을 얻은 transaction이 10,000→9,000을 저장하고 끝나면 다음 요청은 최신 9,000을 읽습니다. 열 성공 뒤 0이 되면 이후 요청의 account.withdraw(1_000)INSUFFICIENT_BALANCE를 냅니다.

일반 findById로 모두 같은 오래된 balance를 읽고 바깥에서 계산하면 overspend나 lost update가 생길 수 있습니다. 다만 이 테스트는 공정한 lock 순서, starvation 부재, 임의 부하의 처리량을 보장하지 않습니다.

실제 기술 이름AccountRepository.findOneForUpdate, pessimistic row lock, serialized balance mutation

16. transaction 문자열에 requestId를 넣으면 멱등 처리가 완성되나요?

아닙니다. solution은 correlation 문자열을 WITHDRAW: + accountId + : + requestId로 만들지만, 같은 idempotency key를 원자적으로 claim하거나 같은 payload인지 비교하지 않습니다. 현재 fixture도 withdraw-0부터 withdraw-19까지 모두 다른 requestId만 보냅니다.

따라서 이 결과는 같은 요청을 재전송해도 돈이 한 번만 움직인다는 증거가 아닙니다. requestId 기록은 추적 단서일 수 있지만, duplicate ownership과 replay 응답은 다음 주의 별도 계약입니다.

실제 기술 이름correlation ID versus idempotency claim, unique fixture request IDs, duplicate-replay non-coverage

17. day 3의 ConcurrentWithdraw20IT는 새 테스트인가요?

아닙니다. day 2에서 도입한 같은 class의 같은 한 개 @Test를 정답 없이 다시 설명하고 Green을 회수하는 delayed retrieval입니다. selector 실행 노출은 하나 늘지만 unique test 수와 W11-new test 수는 늘지 않습니다.

day 2는 learner-authored Red→Green workflow이고 day 3은 provided fixture verified 회수입니다. 동일 파일·동일 메서드를 새 기능 두 개처럼 세지 않습니다.

실제 기술 이름W11D3 carried-in-week delayed retrieval, same ConcurrentWithdraw20IT selector, no new method

18. 1부터 30의 median 15.5와 nearest-rank p95 29는 어떻게 나오나요?

even_median_and_nearest_rank_p95_match_the_independent_vector는 1.0부터 30.0까지를 사용합니다. 표본 수가 짝수 30이므로 median은 15번째 값과 16번째 값의 평균 (15 + 16) / 2 = 15.5입니다.

nearest-rank 위치는 ceil(30 × 0.95) = 29이고 1-based 29번째 값은 29.0입니다. 다른 percentile 구현이 보간값을 만들 수 있으므로 이 결과를 모든 통계 도구의 유일 정의라고 말하면 안 됩니다.

실제 기술 이름ConcurrencyHarnessTest.even_median_and_nearest_rank_p95_match_the_independent_vector, even median, nearest-rank p95

일부터 삼십의 median 십오점오와 nearest rank p95 이십구
그림 한눈에: 독립적인 작은 벡터로 median과 이 코드의 p95 정의를 먼저 고정합니다.

19. before/after sample contract는 무엇을 허용하고 거절하나요?

valid input은 variant before 30행과 after 30행, 모두 60행입니다. validateAndSummarize는 group key가 정확히 두 이름인지, 각 group size가 정확히 30인지 검사한 뒤 variant별 Stats를 만듭니다. 테스트는 반환 Map이 두 key만 가지는지 확인합니다.

valid.subList(0, 59)인 한 줄 부족 입력, other 한 줄을 더한 세 번째 variant, 빈 list는 모두 IllegalArgumentException이어야 합니다. 하지만 before보다 after가 더 빠른지, sample 단위가 밀리초인지, 측정 환경이 같은지는 이 테스트가 확인하지 않습니다.

실제 기술 이름benchmark_contract_requires_exactly_before_and_after_with_thirty_rows_each, exact variant and cardinality schema

before와 after 각 삼십 행만 허용하는 비교 입력 계약
그림 한눈에: 통계 계산 전에 variant 집합과 각 30행이라는 입력 계약부터 fail-closed로 확인합니다.

20. 이 harness가 30회 benchmark나 운영 TPS를 보장하나요?

아닙니다. 소스의 30은 실제 시스템을 30회 반복 실행한 수가 아니라 Sample 값 1..30과 before/after 각 30행짜리 입력 계약입니다. runId·seed를 바꾸며 30회 stress를 수행하는 loop나 structured log overhead 비교 코드는 없습니다.

모형 1,000 task도 실제 DB withdrawal 1,000건이 아닙니다. CPU, database pool, network, logging, sustained load를 포함한 운영 capacity나 TPS 주장을 만들 수 없습니다.

실제 기술 이름synthetic concurrency harness, statistics schema test, no real 30-run benchmark or production-capacity guarantee

21. day 5에서 이월된 SortedLockTransferIT의 정확한 fixture는 무엇인가요?

W10D3에서 이어 온 oppositeDirectionsFinishWithoutDeadlockAndPreserveTotal 한 메서드입니다. 두 account는 각각 10,000, pairs=10, tasks=20, pool size=20, 각 transfer amount=100입니다. A→B requestId ab-0..9와 B→A ba-0..9를 ready 10초 뒤 start latch로 풉니다.

각 Future를 최대 30초씩 기다려 예외 없이 회수한 뒤 두 balance 합 20,000과 TRANSFER ledger의 signed_amount 합 0을 assertion합니다. W11에서는 새 시험이 아니라 이월 회귀 한 개입니다.

실제 기술 이름SortedLockTransferIT.oppositeDirectionsFinishWithoutDeadlockAndPreserveTotal, carried W10D3 regression

이월 정렬 이체 스무 작업의 잔액 합 이만과 원장 합 영
그림 한눈에: 스무 완료, balance total 20,000, transfer-ledger signed sum 0이 직접 증거입니다.

22. 이름에 WithoutDeadlock이 있으면 무엇까지 증명하나요?

이 fixture의 정렬된 production path에서 스무 Future가 각각 제한 안에 예외 없이 반환했다는 뜻으로 읽어야 합니다. unsafe path로 실제 deadlock을 재현하는 비교 test, victim exception 분류, 자동 retry가 없습니다.

각 account가 반드시 10,000인지, transaction row가 정확히 20인지, ledger row가 정확히 40인지, 공정성·처리량·모든 부하의 deadlock freedom도 assertion하지 않습니다. direct final assertions는 completion, balance sum, ledger signed sum입니다.

실제 기술 이름sorted-lock fixture boundary, no actual deadlock reproduction, no retry, no universal guarantee

23. day 6 class selector는 어떤 네 테스트를 한꺼번에 실행하나요?

com.example.financialcore.ConcurrencyHarnessTest class selector는 fixed_pool_bounds_observed_activity_and_accounts_for_all_tasks, ready_timeout_is_a_failure_and_never_a_green_run, even_median_and_nearest_rank_p95_match_the_independent_vector, benchmark_contract_requires_exactly_before_and_after_with_thirty_rows_each 네 메서드를 실행합니다.

day 6이 selector 한 번이면서 @Test 실행 네 번인 이유입니다. 이 네 개는 모두 W11D1 파일에 이미 있었으므로 day 6에 신규 test가 네 개 더 생긴 것도 아닙니다.

실제 기술 이름class-level JUnit selector, four-method execution surface, delayed full-contract retrieval

24. SQL Q13은 어느 표에서 시작하고 결과 한 행은 무엇인가요?

W11-SQL-Q13의 제목은 “거래별 debit·credit 원장 두 건”이고 핵심은 1:N JOIN입니다. 한 business transaction에서 시작해 business_tx.tx_id = ledger_entry.tx_id로 원장을 붙이면 출력 한 행은 그 거래에 속한 ledger entry 한 건입니다.

workbook seed의 balanced transfer tx ...0301을 고르는 예시에서는 TRANSFER_OUT -300TRANSFER_IN +300 두 행이 나옵니다. 거래 한 행이 관계상 원장 두 행으로 늘어난 이유를 시작 table과 cardinality로 설명해야 합니다.

실제 기술 이름W11-SQL-Q13, business_tx JOIN ledger_entry, one-to-many transaction-to-ledger grain

25. Q13에서 모든 transfer가 항상 두 행이라고 말해도 되나요?

안 됩니다. seed는 반례로 requestId REQ-302인 거래에 TRANSFER_OUT -200 한 행만 두고 대응하는 TRANSFER_IN을 일부러 빼 놓았습니다. 모든 tx_type='TRANSFER'를 inner join하면 REQ-301의 두 행과 REQ-302의 한 행으로 총 3행입니다. REQ-301만 필터하면 정확히 2행인 학습 예시가 됩니다.

reference project에는 answers/Q13.sql canonical learner answer가 없습니다. prompt-only 문제이므로 필터·가정·예상 cardinality와 반례를 적고 실제 실행 evidence를 남겨야 합니다. 예시 SQL을 제공 정본이라고 부르지 않습니다.

실제 기술 이름partial-transfer counterexample, seed output 2 rows for REQ-301 versus 3 rows for all transfers including REQ-302, canonical answer absent

거래 삼백일의 두 원장 행과 거래 삼백이의 불완전 한 행
그림 한눈에: 1:N join의 정상 두 행과 일부러 빠진 credit 반례를 함께 봐야 “두 행”을 보편 법칙으로 오해하지 않습니다.

26. SQL Q14에서 0계좌 고객을 포함하려면 어떻게 해야 하나요?

W11-SQL-Q14의 제목은 “고객별 계좌 수”이고 핵심은 GROUP BY·COUNT입니다. 0계좌 고객을 포함하는 선택에서는 customer에서 시작해 account를 LEFT JOIN하고 customer별로 묶은 뒤 COUNT(a.account_id)를 사용합니다. COUNT(*)는 account가 없어도 남은 customer 행을 1로 셀 수 있어 맞지 않습니다.

seed는 customer 6명, account 8개입니다. customer 1~6의 account 수는 각각 2, 2, 2, 1, 0, 1이므로 결과는 6행입니다. customer 5 계좌없음이 count 0으로 남습니다.

실제 기술 이름W11-SQL-Q14, customer LEFT JOIN account, GROUP BY, COUNT(a.account_id), six-row include-zero choice

고객 여섯 명의 계좌 수 이 이 이 일 영 일
그림 한눈에: LEFT JOIN과 nullable-side PK count로 0계좌 고객까지 여섯 행에 남깁니다.

27. Q14에서 0계좌 고객을 제외하는 답은 틀린가요?

문제의 통과 문구는 “0계좌 고객 포함 여부 결정”입니다. 제외하기로 명시했다면 account가 있는 고객만 inner join해 1·2·3·4·6번 고객의 다섯 행을 만드는 선택도 설명 가능한 예시입니다. 다만 무엇을 선택했는지 숨기고 행 수만 제시하면 안 됩니다.

reference project에는 answers/Q14.sql canonical learner answer도 없습니다. 이 노트는 모든 고객을 남기는 6행 예시를 택하지만, 그 선택을 유일한 제공 정본으로 주장하지 않습니다.

실제 기술 이름Q14 assumption-driven cardinality, LEFT JOIN six rows versus INNER JOIN five rows, canonical answer absent

28. W11 월~토가 직접 보장하는 것과 보장하지 않는 것을 한 번에 정리하면?

직접 보장하는 핵심은 bounded synthetic harness의 상한·완전성·ready failure, 독립 벡터의 median 15.5·nearest-rank p95 29, before/after 각 30행 schema, 한 번의 실제 20건 출금에서 10/10/0·balance0·ledger10, 이월 정렬 이체에서 모든 Future·balance sum20,000·ledger sum0입니다.

직접 보장하지 않는 것은 실제 30회 stress, 실제 DB 1,000건, 운영 TPS·latency, 같은 requestId 멱등성, 어느 sequence가 성공하는지, ledger 각 행의 금액·부호 전체, 실제 deadlock 재현·retry, 계좌별 10,000, Q13/Q14 canonical answer입니다. 다음 W12는 바로 이 중 “같은 의도가 여러 번 와도 한 번만 처리”하는 경계를 다룹니다.

실제 기술 이름W11 executable guarantee/non-guarantee boundary, handoff to W12 idempotency