오류·예외·동시성의 첫 직관
파일이 없거나 결제가 거절될 수 있고, 두 사용자가 같은 마지막 재고를 동시에 주문할 수 있다. 정상 경로를 한 줄씩 차례대로 실행한다고만 가정하면 첫 번째 문제의 실패 경로를 놓치고, 두 번째 문제에서 실행 순서에 따라 결과가 바뀌는 이유를 설명하지 못한다.
정상 순서만 그린 프로그램은 현실에서 깨진다
실제 프로그램에서는 파일이 없거나 결제가 거절될 수 있다. 두 사용자가 같은 마지막 재고를 동시에 주문할 수도 있다. 그런데 정상 경로를 한 줄씩 차례대로 실행한다고만 가정하면 앞의 문제에서는 실패 경로를 놓치고, 뒤의 문제에서는 실행 순서에 따라 결과가 바뀌는 이유를 설명하지 못한다.
이 두 문제를 다루는 개념이 예외와 동시성이다. 예외는 정상 흐름에서 처리하기 어려운 실패를 전달하고, 동시성은 여러 작업의 진행을 겹치게 구성한다. 경쟁조건은 겹친 작업이 공유상태를 다룰 때 순서에 따라 잘못된 결과가 생기는 조건이다.
먼저 되짚기: 흐름과 상태를 함께 본다
제어흐름은 다음 실행 위치를, 상태는 같은 입력에도 다음 행동을 달라지게 하는 기억을 설명했다. 예외는 제어흐름을 실패 처리 위치로 바꾸고, 동시 작업은 같은 상태를 서로 다른 순서로 읽고 쓸 수 있다. 이번 절은 이미 배운 흐름과 상태에 실패와 겹침을 더한다.
실패 경로와 겹치는 실행을 기존 흐름에 더한다
제어흐름에는 정상 진행 경로와 예외 전파 경로가 있다. 그리고 공유상태를 다루는 작업이 겹칠 때는 재고 변경의 실행 순서가 규칙을 깨뜨리는지 따로 확인해야 한다. 즉 예외와 경쟁조건은 서로 다른 축의 문제다.
예외
예외(exception)는 정상 흐름으로 처리하기 어려운 실패나 비정상 상태를 전달하는 메커니즘이다. 예외가 발생하면 현재 연산의 정상 진행이 멈추고, 그 예외를 처리할 수 있는 가까운 호출자까지 실패 정보가 전달된다. 처리할 수 없는 예외는 더 위로 보내되, 필요한 자원 정리는 그 전에 반드시 해야 한다.
동시성
동시성(concurrency)은 여러 작업의 진행이 시간상 겹치도록 구조화하는 성질이다. 한 CPU가 작업을 짧게 번갈아 실행해도 진행 기간은 겹칠 수 있다. 여러 CPU가 같은 순간에 실제로 실행하는 병렬성과 관련되지만, 동시성은 우선 여러 진행의 상호작용을 다루는 구조에 초점을 둔다.
경쟁조건 전에 동시성을 다시 잇는다
동시성은 여러 작업의 진행 구간이 겹치도록 구조화하는 성질이다. 결과의 올바름이 이 겹침의 우연한 순서에 의존하면 경쟁조건이 된다.
동시 실행 자체가 버그는 아니다
두 작업이 겹쳐도 공유상태를 안전하게 다루면 결과는 올바를 수 있다. 그래서 두 작업의 단계가 끼어드는 여러 실행 순서(interleaving) 가운데 규칙을 깨는 것이 있는지를 확인한다.
경쟁조건
경쟁조건은 실행 순서에 따라 공유상태의 결과가 달라지는 결함 조건이다. 단순히 작업이 동시에 존재한다는 뜻이 아니다. 올바름이 우연한 실행 순서나 속도에 의존하고, 가능한 순서 중 일부가 규칙을 깨뜨릴 때 경쟁조건이 된다.
마지막 재고 하나가 두 번 팔리는 과정
재고가 1이고 주문 A와 B가 다음 세 단계를 각각 수행한다고 하자.
| 시간 | 주문 A | 주문 B | 관찰 |
|---|---|---|---|
| 1 | 재고 1을 읽음 | 재고 1 | |
| 2 | 재고 1을 읽음 | 두 작업이 같은 옛 값을 가짐 | |
| 3 | 1 > 0이므로 승인 | 승인 1건 | |
| 4 | 1 > 0이므로 승인 | 승인 2건 | |
| 5 | 재고 0을 기록 | 재고 0 | |
| 6 | 재고 0을 기록 | 승인 2건·재고 0: 업무 규칙 위반 |
각 주문을 홀로 실행하면 맞지만, 읽기–판단–쓰기 단계가 서로 끼어들면서 두 번 승인된다. 그래서 이 세 단계를 다른 작업이 끼어들 수 없는 하나의 변경으로 묶거나, 저장된 값이 여전히 읽었던 값일 때만 갱신하도록 검사해야 한다.
주문 처리 중 저장 실패가 발생하면 예외는 실패 정보를 상위 흐름으로 전달한다. 그러나 예외를 잡았다고 해서 이미 수행한 결제나 재고 변경이 저절로 취소되지는 않는다. 어떤 상태가 바뀌었는지 기록하고 되돌리기, 재시도, 담당자 확인 중 하나를 골라야 한다.
ER-1: 재고 1개에 두 주문이 겹치는 순서를 적는다
초기 재고가 1이고 두 작업 A, B가 각각 “재고가 1 이상이면 1 감소 후 성공”을 수행한다고 하자.
| 순서 | A | B | 재고 |
|---|---|---|---|
| 1 | 1 읽음 | 1 | |
| 2 | 1 읽음 | 1 | |
| 3 | 성공 판단·0 기록 | 0 | |
| 4 | 이미 읽은 1로 성공 판단·0 기록 | 0 |
두 주문이 모두 성공했으므로 업무 규칙 “재고 1개는 한 주문에만 배정”을 위반한다. 마지막 숫자가 0이라는 이유만으로 정상이라고 판정하지 않는다.
이처럼 A와 B의 단계가 번갈아 끼어드는 실행 순서가 앞에서 말한 interleaving이다. 보호된 예약에서는 A가 읽기·판단·쓰기를 마칠 때까지 B가 같은 재고의 변경 구간에 들어가지 못하게 한다. 그러면 A는 재고 1을 읽고 한 개를 예약해 0을 기록한 뒤 성공한다. 그다음 B는 새 재고 0을 읽어 부족으로 거절되므로 결과는 성공 1건, 거절 1건, 재고 0이다. 여기서는 이 구간을 하나처럼 보이게 보호하는 것을 원자적 예약이라고 부른다. 그래서 중간의 검사와 차감을 다른 작업이 사이에 끼어들 수 있는 두 연산으로 따로 공개하지 않는다. 잠금은 이런 보호 구간에 한 작업만 들어가게 하는 방법이며, 구체적인 구현은 뒤에서 배운다.
결제 예외도 상태를 따라가 보자. 주문42가 재고를 예약한 뒤 결제를 호출했는데 통신 응답을 받지 못했다고 가정한다. 이때는 결제가 안 되었다고도, 성공했다고도 확정할 수 없다. 그래서 주문은 확인대기로 두고, 결제사에 주문42의 기존 결과를 확인한다. 이미 승인되었다면 그 결과를 재사용해 확정하고, 명확하게 거절되었다면 예약을 해제해 재고를 되돌린다. 이를 위해 같은 주문42의 요청을 반복해도 새 청구가 생기지 않는다는 규칙을 결제 연동에 둔다. 결과가 불명확한 동안 즉시 다시 청구하는 방식은 중복 청구를 낳고, 재고를 풀어 다른 주문에 팔아 버리는 방식은 주문42와 한 재고 약속을 깨므로 둘 다 제외한다.
예외를 처리하는 지점에서는 원인과 주문번호, 현재 상태를 기록한다. 파일이나 보호 구간을 사용 중이었다면 실패 여부와 관계없이 닫거나 해제한다. 예약이라는 업무 상태를 되돌리는 판단과 잠금 같은 실행 자원을 해제하는 동작은 서로 다른 일이다. 예외를 잡았다는 사실만으로 둘 다 끝났다고 생각하지 않는다.
반면 외부 결제 호출이 실패해 예외가 발생하는 문제는 경쟁조건과 다른 축이다. 그러니 예외 처리와 동시성 제어를 한 기법으로 뭉개지 않는다.
무엇을 어떻게 처리할 것인가
| 상황 | 적절한 표현·통제 | 피해야 할 반응 |
|---|---|---|
| 예상 가능한 업무 거절 | 결괏값과 이유로 정상 분기 | 모든 거절을 시스템 예외로 처리 |
| 계속할 수 없는 기술 실패 | 예외로 전파하고 처리할 수 있는 지점에서 복구·변환 | 실패를 삼키고 성공처럼 진행 |
| 독립 작업의 대기 겹침 | 동시 실행으로 대기시간 활용 | 공유상태 통제 없이 무작정 병렬화 |
| 공유상태 변경 | 한 번에 처리되는 구간·버전 검사·메시지 전달 | 실행 속도가 충분히 다를 것이라 가정 |
동시성을 늘리면 자원을 기다리는 동안 다른 일을 진행해 처리량을 높일 수 있다. 하지만 그만큼 순서와 공유상태, 취소, 실패가 얽히는 경우가 복잡해진다. 그래서 먼저 소유권을 나누어 공유를 줄이고, 정말 공유해야 하는 상태에만 동기화를 적용한다.
실패조건과 방어
예외 삼키기는 실패를 기록하거나 복구하지 않고 빈 결과로 바꿔 버리는 처리다. 예외는 처리할 수 있는 지점에서만 잡고, 원인과 문맥을 보존한다.
예외를 지나치게 넓게 잡으면 서로 다른 원인을 모두 같은 실패로 취급하게 된다. 복구 행동이 같은 실패만 함께 처리한다.
검사와 실행을 분리하면 “재고가 있다”를 확인한 뒤 실제로 차감하기까지 다른 작업이 끼어들 수 있다. 판단과 변경을 하나의 보호된 연산으로 묶는다.
잠금 범위가 크면 경쟁은 막지만 독립된 작업까지 기다리게 된다. 서로 상대가 가진 자원을 기다리는 교착이 생길 수도 있다. 보호할 불변조건에 필요한 최소 범위만 잠그고, 자원을 얻는 순서를 통일한다.
한 번 재현해 보고 안전하다고 판정하면 특정 실행 순서만 시험한 결과를 일반화하게 된다. 반복 실행, 부하, 강제 지연으로 여러 순서를 만들어 보고 결과가 불변조건을 지키는지 검증한다.
자체 10점 문제
예외·동시성·경쟁조건의 차이와 통제 원칙을 설명하시오.
요구어는 설명이다. 실패 전파와 겹치는 실행, 순서 의존 결함을 구분한다.
10점 풀이 방향과 모범답안 보기
10점 답안 목차
- 예외와 실패전파
- 동시성
- 경쟁조건
- 공유상태 interleaving
- 자원정리·동기화 경계
10점 답안 도식
| 개념 | 판별 조건 | 통제 |
|---|---|---|
| 예외 | 정상 진행을 계속할 수 없는 실패 | 실패 전파·처리 지점의 복구·자원 정리 |
| 동시성 | 여러 작업의 진행 구간이 겹침 | 소유권·취소·상호작용 설계 |
| 경쟁조건 | 겹친 작업의 우연한 순서가 규칙을 위반 | 공유를 줄이고 판단·변경을 보호 |
모범답안 축약본: 실패 경로와 실행 순서를 설계한다
예외는 정상 반환만으로 처리하기 어려운 실패를 호출자에게 전달하는 제어 경로지만, 이미 변경한 상태를 자동으로 복구하지는 않는다. 동시성은 둘 이상의 작업 진행구간이 겹치는 성질이며, 한 실행 장치가 작업을 번갈아 실행하는 경우도 포함한다. 경쟁조건은 실행 순서에 따라 결과의 올바름이 달라지는 결함이다. ER-1에서는 두 주문이 각각 재고 1을 읽고 0을 덮어써 최종 재고가 0인데도 두 건을 승인한다. 공유 변경을 줄이고 재고 확인과 차감을 하나의 보호된 원자적 예약으로 묶으며, 자원 획득 순서를 통일한다. 예외 시 현재 업무 상태를 확인해 보상·기존 결과 확인·재시도 경로를 선택하고 실행 자원을 반드시 해제한다. 다양한 실행 순서에서 재고 비음수뿐 아니라 한 개당 성공 한 건이라는 불변조건도 검증한다.
자체 25점 문제
재고 1개 동시 주문과 결제 예외를 함께 처리하는 주문 흐름을 설계하시오.
요구어는 설계다. 공유상태의 원자성, 예외 전파와 정리, 재시도와 중복 처리의 기준을 제시한다.
25점 풀이 방향과 모범답안 보기
25점 답안 목차
- 공유 재고 규칙
- read-check-update 원자성
- 충돌/실패 반환
- 결제 예외 경로
- 자원정리
- 같은 주문의 반복 요청과 중복 처리 방지
- interleaving 테스트
- 모니터링·오류 분류
25점 답안 도식
결제 결과가 불명확하면 기존 결과를 확인한다 · 상태 따라가기
주문 42의 재고 읽기·판단·차감을 보호하며 아직 결제를 요청하지 않는다. 모든 단계에서 주문 42·재고·결제·확인의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
재고 원자 예약이 실패하면 거절한다. 예약 성공 후 주문42의 결제를 요청하며 승인이 확인되면 같은 결과로 주문을 확정하고, 명확히 거절되면 예약을 해제한다. 응답 미확인은 확인대기로 남겨 주문42의 기존 결과를 조회한다. 이 조회가 새 청구를 만드는 재요청이 되어서는 안 되며, 실행 자원 해제는 업무 예약 해제와 별개다.
이 예의 조건
- 재고 원자 예약이 실패하면 거절한다. 예약 성공 후 주문 42의 결제를 요청하며 승인이 확인되면 같은 결과로 주문을 확정하고, 명확히 거절되면 예약을 해제한다. 응답 미확인은 확인대기로 남겨 주문 42의 기존 결과를 조회한다. 이 조회가 새 청구를 만드는 재요청이 되어서는 안 되며, 실행 자원 해제는 업무 예약 해제와 별개다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 대상 | 원 설명 |
|---|---|
| 재고 원자 예약 | 읽기·판단·차감 보호 |
| 예약 성공? | 원그림의 대상 |
| 재고 부족 거절 | 원그림의 대상 |
| 주문 42 결제 | 기존 결과 재사용 계약 |
| 결제 결과? | 원그림의 대상 |
| 주문 확정 | 같은 승인 결과 |
| 예약 해제 | 명확한 거절일 때 |
| 확인대기 | 기존 결과 조회 |
| 연결 | 원 라벨·방향 |
|---|---|
| 재고 원자 예약 → 예약 성공? | 라벨 없는 연결 / 정방향 / 실선 |
| 예약 성공? → 주문 42 결제 | 성공 / 정방향 / 실선 |
| 예약 성공? → 재고 부족 거절 | 실패 / 정방향 / 실선 |
| 주문 42 결제 → 결제 결과? | 라벨 없는 연결 / 정방향 / 실선 |
| 결제 결과? → 주문 확정 | 승인 확인 / 정방향 / 실선 |
| 결제 결과? → 예약 해제 | 명확한 거절 / 정방향 / 실선 |
| 결제 결과? → 확인대기 | 응답 미확인 / 정방향 / 실선 |
| 확인대기 → 결제 결과? | 기존 조회 / 정방향 / 점선 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 원자 예약 성공 전 주문 42의 재고 읽기·판단·차감을 보호하며 아직 결제를 요청하지 않는다. |
|
|
| 예약 성공 뒤 결제 요청 예약 성공을 확인한 뒤 주문 42의 결제를 요청하고 결과를 기다린다. |
|
|
| 대안 1: 승인 확인 공통 시작점인 예약 성공·결제 결과 대기에서 승인 확인이 나온 경우다. |
|
|
| 대안 2: 명확한 거절 예약 성공·결제 결과 대기로 재설정하고 명확한 거절이 나온 경우를 본다. |
|
|
| 대안 3: 응답 미확인 예약 성공·결제 결과 대기로 재설정하고 응답을 확인하지 못한 경우를 본다. |
|
|
결제 결과가 불명확하면 기존 결과를 확인한다 · 상태 따라가기
주문 42의 재고 읽기·판단·차감을 보호하며 아직 결제를 요청하지 않는다. 모든 단계에서 주문 42·재고·결제·확인의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
재고 원자 예약이 실패하면 거절한다. 예약 성공 후 주문42의 결제를 요청하며 승인이 확인되면 같은 결과로 주문을 확정하고, 명확히 거절되면 예약을 해제한다. 응답 미확인은 확인대기로 남겨 주문42의 기존 결과를 조회한다. 이 조회가 새 청구를 만드는 재요청이 되어서는 안 되며, 실행 자원 해제는 업무 예약 해제와 별개다.
이 예의 조건
- 재고 원자 예약이 실패하면 거절한다. 예약 성공 후 주문 42의 결제를 요청하며 승인이 확인되면 같은 결과로 주문을 확정하고, 명확히 거절되면 예약을 해제한다. 응답 미확인은 확인대기로 남겨 주문 42의 기존 결과를 조회한다. 이 조회가 새 청구를 만드는 재요청이 되어서는 안 되며, 실행 자원 해제는 업무 예약 해제와 별개다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 대상 | 원 설명 |
|---|---|
| 재고 원자 예약 | 읽기·판단·차감 보호 |
| 예약 성공? | 원그림의 대상 |
| 재고 부족 거절 | 원그림의 대상 |
| 주문 42 결제 | 기존 결과 재사용 계약 |
| 결제 결과? | 원그림의 대상 |
| 주문 확정 | 같은 승인 결과 |
| 예약 해제 | 명확한 거절일 때 |
| 확인대기 | 기존 결과 조회 |
| 연결 | 원 라벨·방향 |
|---|---|
| 재고 원자 예약 → 예약 성공? | 라벨 없는 연결 / 정방향 / 실선 |
| 예약 성공? → 주문 42 결제 | 성공 / 정방향 / 실선 |
| 예약 성공? → 재고 부족 거절 | 실패 / 정방향 / 실선 |
| 주문 42 결제 → 결제 결과? | 라벨 없는 연결 / 정방향 / 실선 |
| 결제 결과? → 주문 확정 | 승인 확인 / 정방향 / 실선 |
| 결제 결과? → 예약 해제 | 명확한 거절 / 정방향 / 실선 |
| 결제 결과? → 확인대기 | 응답 미확인 / 정방향 / 실선 |
| 확인대기 → 결제 결과? | 기존 조회 / 정방향 / 점선 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 원자 예약 성공 전 주문 42의 재고 읽기·판단·차감을 보호하며 아직 결제를 요청하지 않는다. |
|
|
| 예약 성공 뒤 결제 요청 예약 성공을 확인한 뒤 주문 42의 결제를 요청하고 결과를 기다린다. |
|
|
| 대안 1: 승인 확인 공통 시작점인 예약 성공·결제 결과 대기에서 승인 확인이 나온 경우다. |
|
|
| 대안 2: 명확한 거절 예약 성공·결제 결과 대기로 재설정하고 명확한 거절이 나온 경우를 본다. |
|
|
| 대안 3: 응답 미확인 예약 성공·결제 결과 대기로 재설정하고 응답을 확인하지 못한 경우를 본다. |
|
|
25점 모범답안
재고 확인과 감소를 서로 분리된 읽기·쓰기로 두지 않고 하나의 논리적 예약 연산으로 묶어, 한 주문만 성공하게 한다. ER-1의 두 작업 interleaving을 재현해 둘 다 성공하는 경로가 사라졌는지 확인한다.
재고 예약 뒤 결제 호출에서 예외가 발생하면 예외를 숨겨 성공으로 처리하지 않고, 주문 상태에 맞게 예약 해제나 보상 정책을 실행한다. 재시도할 때는 같은 주문이 두 번 청구되지 않도록 요청 식별자를 두고, 이미 있는 결과를 재사용하는 규칙을 정한다.
동시성 제어는 순서에 의존하는 공유상태에, 예외 처리는 실패 전파와 정리에 각각 적용한다. 두 문제를 구분하므로 로그와 테스트에서도 interleaving 실패와 외부호출 실패를 따로 남긴다.
구체적으로는 초기 재고 1에서 A의 예약이 끝난 뒤에야 B가 새 재고 0을 읽도록 보호해 성공 1건과 거절 1건을 얻는다. A의 주문42에서 결제 응답이 불명확하면 확인대기로 두고 기존 승인 결과를 확인한다. 승인이 확인되면 같은 결과로 주문을 확정하고, 명확하게 거절된 경우에만 예약을 해제한다. 실행 자원은 실패할 때도 해제하고, 주문번호와 실패 원인, 예약·결제 상태를 담당자가 확인할 수 있게 남긴다. 무조건 다시 청구하는 방식과 결제 상태가 불명확한데 재고를 바로 푸는 방식은 제외한다. 시험에서는 A와 B의 순서를 바꾼 예약, 그리고 승인·거절·응답없음 뒤 같은 주문을 다시 요청하는 경우를 각각 대조한다.
출처와 적용 경계
본문은 안정된 예외·동시성 기초와 자체 재고 interleaving 예를 사용한다. 실제 lock, transaction, actor, async 예외의 의미는 사용하는 언어와 런타임, 저장소 제품의 문서를 확인한다.
30초 정리
- 예외는 정상 흐름으로 처리하기 어려운 실패를 전달하지만 이미 바뀐 상태를 자동으로 되돌리지는 않는다.
- 동시성은 여러 작업의 진행을 겹치게 하며, 병렬 실행 여부와 별개로 상호작용을 설계해야 한다.
- 경쟁조건은 올바름이 실행 순서에 의존하는 결함이므로 공유를 줄이고 판단과 변경을 안전한 한 단위로 묶는다.
자기교정과 조건 변경
| 잘못 쓴 답 | 수정 | 복습 |
|---|---|---|
| 동시 작업이 있으면 항상 race다 | 순서에 따라 규칙이 깨지는지 본다 | 동시성 |
| 최종 재고가 0이면 두 주문 성공도 정상이다 | 성공 건수와 업무 불변조건을 함께 본다 | ER-1 |
| 예외처리만 잘하면 race도 해결된다 | 실패전파와 공유상태 동기화를 분리한다 | 비교 |
| lock을 쓰면 모든 오류가 사라진다 | 교착·범위·외부호출 등 다른 실패가 남는다 | 실패 |
| 재시도는 실패한 호출을 그냥 다시 하면 된다 | 같은 주문의 기존 결과와 중복 부작용을 확인한다 | ER-1 |
조건을 바꿔 보자. 재고가 2개라면 두 주문의 성공은 허용될 수 있다. 하지만 세 주문이 끼어드는 interleaving까지 같은 불변조건으로 검사해야 한다.
결제가 예약보다 먼저 실행되면 재고 확보에 실패했을 때 결제를 취소하거나 환불해야 하는 문제가 생긴다. 그래서 단계의 순서와 보상 방법을 다시 설계한다.
공유상태가 없다면 동시성 자체는 남아도 이 재고 race의 원인은 사라질 수 있다.