분산·다중처리와 부분 장애
공유 메모리의 다중처리와 여러 OS의 분산 실행을 구분하고 투명성·재시도·부분 장애를 작은 상태 추적으로 이해합니다.
코어를 늘리는 일과 컴퓨터를 여러 대 연결하는 일은 둘 다 작업량을 늘릴 수 있지만 운영체제가 조정하는 경계가 다릅니다. 여러 코어는 한 컴퓨터의 물리 메모리에 함께 접근할 수 있습니다. 다른 컴퓨터의 주소 0x1000은 내 컴퓨터의 같은 데이터 위치를 가리키지 않습니다. 네트워크로 값을 전달하는 별도 규약이 필요합니다.
강결합과 약결합의 자원 경계
다중처리 시스템은 여러 프로세서가 한 시스템의 자원을 함께 사용합니다. SMP는 여러 프로세서가 대칭적인 역할로 같은 OS의 작업을 실행하는 구조를 설명합니다. 비대칭 다중처리에서는 프로세서별 역할을 구분해 특정 프로세서가 관리 작업을 맡을 수 있습니다. 모든 제품의 구성이나 성능을 하나의 모델로 단정하지 않습니다.
공유 메모리라도 NUMA에서는 접근 위치에 따라 비용이 달라지고, 캐시·동기화·부하 분산 때문에 코어 수에 비례해 항상 빨라지지 않습니다. 앞의 멀티코어 스케줄링은 같은 OS가 코어·런 큐·메모리 친화성을 함께 조율하는 사례입니다.
분산 시스템은 각 노드에 독립적인 메모리와 OS가 있고 네트워크 메시지로 협력합니다. 한 노드의 장애가 다른 노드 전체의 정지를 뜻하지 않을 수 있어 부분 장애를 고려해야 합니다.
| 관점 | 한 OS의 다중처리 | 여러 노드의 분산 실행 |
|---|---|---|
| 데이터 접근 | 공유 물리 메모리·캐시/메모리 정책 | 메시지·직렬화·원격 서비스 |
| 조정 | 스케줄러·잠금·메모리 관리 | 프로토콜·타임아웃·재시도 |
| 장애 | 시스템 내부 장애 경계 | 노드/링크별 부분 실패 가능 |
| 비용 | NUMA·캐시·경쟁 | 전송 지연·중복·연결 단절 |
다른 프로세스의 같은 가상 주소가 자동으로 같은 데이터를 가리키지는 않습니다. 같은 프로세스의 주소 공간을 쓰는 스레드 또는 명시적으로 공유한 메모리 영역을 구분합니다. SMP의 공유는 시스템의 물리 메모리와 조정 자원에 관한 설명입니다.
네트워크 OS와 분산 OS의 목적
네트워크 OS는 각 컴퓨터의 경계를 드러내면서 원격 파일·로그인·서비스 공유를 제공합니다. 분산 OS의 목표 중 하나는 여러 자원을 단일한 시스템처럼 보이게 하는 것입니다. 투명성은 사용자가 위치·복제·이동 같은 일부 차이를 의식하지 않아도 쓰게 하는 성질입니다.
투명성이 모든 실패를 없애지는 않습니다. 원격 호출이 느리거나 끊어질 수 있다는 사실을 완전히 숨기면 호출자가 실패 정책을 정하기 어렵습니다. 네이밍은 자원의 안정적인 이름과 실제 위치를 연결하고, 전역 상태는 여러 노드의 서로 다른 관찰을 조율해야 합니다. 한 서버의 현재 변수 하나처럼 즉시 읽는 전역 정보가 항상 있는 것은 아닙니다.
역사적 워크스테이션/프로세서 풀 모델은 작업을 개인 장치에 두거나 공동 자원에 배정하는 차이를 설명합니다. 현대 클러스터의 작업 배치와 연결할 수 있지만 Kubernetes 같은 오케스트레이터를 Linux 커널이나 하나의 분산 OS와 같은 이름으로 취급하지 않습니다.
타임아웃 뒤에는 어떤 상태일까
클라이언트가 서버에 “작업 접수” 요청을 보내고 응답을 못 받았습니다. 가능한 상태는 요청이 서버에 도착하지 않음, 처리 중, 처리 완료 후 응답만 유실됨입니다. 타임아웃은 이 상태들을 모두 구별해 주지 않습니다.
처리 완료 후 응답만 유실됐는데 새 요청으로 재시도하면 같은 일을 두 번 접수할 수 있습니다. 멱등성은 같은 연산을 여러 번 수행한 업무 효과가 한 번 수행한 효과와 같은 성질입니다. 같은 값을 다시 설정하는 연산처럼 요청 번호 없이도 멱등할 수 있으며, 응답 문구까지 항상 같아야 한다는 뜻은 아닙니다.
이 예제는 접수처럼 효과를 추가하는 연산에 요청 식별자와 완료 결과 기록을 붙여 같은 요청의 재전송을 중복 처리하지 않는 기법을 사용합니다. 요청 번호·완료 결과를 효과와 함께 일관되게 보존해야 합니다.
# 네트워크/OS 측정이 아니라 명시적으로 정의한 상태 모델이다.
jobs = []
results = {}
def submit(request_id, payload):
if request_id in results:
return results[request_id]
jobs.append(payload)
result = len(jobs)
results[request_id] = result
return result
first = submit("request-7", "report") # 서버 처리 완료
# 첫 응답이 유실됐다고 가정하고 같은 번호로 재시도
retry = submit("request-7", "report")
print(first, retry, len(jobs)) # 1 1 1첫 호출 뒤 상태는 jobs 길이 1, results에 request-7→1입니다. 재시도는 기록된 결과를 반환해 길이를 늘리지 않습니다. 실제 시스템에서는 두 요청의 동시 도착, 결과 기록 전 장애, 같은 번호에 다른 요청 내용(payload), 저장 수명도 처리해야 합니다. 위 메모리 딕셔너리는 프로세스 재시작에도 보존되는 완전한 구현이 아닙니다.
작업 예외와 프로세스 종료를 나눠 추적하기
jobs와 results는 모두 이 프로세스의 메모리입니다. 따라서 “장애가 났다”는 말만으로 어느 상태가 남는지 정할 수 없습니다. 다음은 프로세스가 계속 살아 있고 한 함수 호출만 예외로 중단되는 경우를 모델링합니다.
jobs = []
results = {}
def submit(request_id, payload, fail_after_effect=False):
if request_id in results:
return results[request_id]
jobs.append(payload)
if fail_after_effect:
raise RuntimeError("effect added before result recorded")
result = len(jobs)
results[request_id] = result
return result
try:
submit("request-7", "report", fail_after_effect=True)
except RuntimeError:
pass # 프로세스는 살아 있으며 jobs에만 한 항목이 남는다.
retry = submit("request-7", "report")
print(retry, len(jobs)) # 2 2: 완료 기록이 없어 중복 추가
# 별도 가정: 영구 저장이 전혀 없는 새 프로세스의 시작 상태
jobs = []
results = {}
restarted = submit("request-7", "report")
print(restarted, len(jobs)) # 1 1: 이전 메모리의 작업도 함께 소실첫 상태는 작업 추가 뒤 예외를 잡고 같은 메모리에서 재시도해 중복이 생깁니다. 두 번째 상태는 새 프로세스의 빈 메모리를 명시한 것이므로 이전 작업과 완료 기록이 모두 없어집니다. 이 코드는 실제 프로세스를 강제 종료하거나 네트워크를 측정한 실험이 아닙니다.
반면 실제 업무 효과가 별도 DB나 작업 큐에 이미 영구 저장됐는데 요청 번호의 완료 기록만 남기지 못했다면, 애플리케이션 프로세스가 종료돼도 그 업무 효과는 남을 수 있습니다. 이 조건에서는 재시도가 중복 효과를 만들 수 있습니다. 효과와 중복 방지 기록을 한 트랜잭션으로 저장하거나, 외부 대상도 같은 요청 번호를 검사하는 설계가 필요한 이유입니다. 메모리 예제와 영구 저장 사례의 실패 경계를 섞지 않습니다.
클라이언트·서버와 미들웨어
클라이언트는 요청하고 서버는 자원을 관리합니다. 화면–업무 서비스–데이터 저장소의 3층 구성은 UI·업무 규칙·지속성의 책임을 나누는 방법입니다. 층이 셋이면 서버가 반드시 세 대라는 뜻은 아닙니다. 미들웨어는 통신·인증·직렬화·서비스 발견 등의 공통 기능을 제공할 수 있습니다.
분산 실행의 이익에는 자원 공유와 확장·가용성 향상 가능성이 있지만, 복제/통신/운영의 추가 비용이 있습니다. 노드를 두 대로 늘렸다는 사실만으로 모든 장애에 두 배 안전해지는 것도 아닙니다. 두 노드가 같은 전원·저장 장치에 의존하면 공동 실패 경계가 남습니다.
다음 그림은 효과와 완료 기록을 같은 DB에 원자적으로 커밋한 정상 경로입니다. DB 파일이 재시작 경계 밖에 남고 동일 요청에서 결과를 조회한다는 전제입니다. 앞의 메모리 모델이나 효과만 저장한 실패 경로에 이 결과를 적용하지 않습니다. 지속 저장과 안전한 재시도에서 실제 최소 코드, worker 종료와 경쟁·충돌·보관 기간을 확인합니다.
완료 기록이 재시작 뒤에도 남아야 결과를 재사용한다
효과와 완료 기록을 같은 트랜잭션에 둔다. 존속한 DB에서 동일 요청을 조회한다. 각 단계의 값과 전환 이유를 비교합니다.
완료 기록이 재시작 뒤에도 남아야 결과를 재사용한다
같은 DB 트랜잭션의 효과와 완료 기록이 확정된 뒤 응답 전에 worker가 죽은 성공 경로입니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 함께 저장부터 다시 조회까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 함께 저장 효과와 완료 기록을 같은 트랜잭션에 둔다. |
|
|
| COMMIT 뒤 crash 둘이 확정됐지만 응답 전에 worker가 죽는다. |
|
|
| 다시 조회 존속한 DB에서 동일 요청을 조회한다. |
|
|
완료 기록이 재시작 뒤에도 남아야 결과를 재사용한다
효과와 완료 기록을 같은 트랜잭션에 둔다. 존속한 DB에서 동일 요청을 조회한다. 각 단계의 값과 전환 이유를 비교합니다.
완료 기록이 재시작 뒤에도 남아야 결과를 재사용한다
같은 DB 트랜잭션의 효과와 완료 기록이 확정된 뒤 응답 전에 worker가 죽은 성공 경로입니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 함께 저장부터 다시 조회까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 함께 저장 효과와 완료 기록을 같은 트랜잭션에 둔다. |
|
|
| COMMIT 뒤 crash 둘이 확정됐지만 응답 전에 worker가 죽는다. |
|
|
| 다시 조회 존속한 DB에서 동일 요청을 조회한다. |
|
|
독립 문제
첫 응답 유실 모델에서 첫 호출의 번호는 request-7이고 재시도는 request-8이면 결과와 작업 수는 어떻게 바뀌나요? 두 번째 모델처럼 jobs.append 뒤 함수 예외만 발생하고 프로세스는 살아 있다면 무엇이 남고, 같은 번호의 재시도는 왜 중복되나요? 영구 저장이 없는 프로세스 자체가 종료돼 새로 시작한 경우에도 이전 작업 하나가 반드시 남는다고 말할 수 있나요? NUMA 메모리 접근과 원격 서비스 호출을 모두 같은 공유 메모리 접근이라고 설명하면 무엇을 빠뜨리나요?
힌트
같은 번호의 결과가 이미 있는지를 먼저 보고, 효과와 증거의 기록 순서를 따릅니다.
풀이
새 번호는 새 요청이므로 첫 결과 1, 재시도 결과 2, 작업 수 2입니다. 함수 예외 뒤 같은 프로세스에서는 jobs에 작업 한 개가 남지만 results에는 완료 기록이 없어 같은 번호의 재시도가 두 번째 작업을 추가합니다. 영구 저장 없이 프로세스가 종료되면 두 메모리 상태 모두 소실되므로 새 프로세스의 작업 수는 첫 재접수 뒤 1입니다. 별도 저장소에 업무 효과만 영구 저장됐다는 조건이 있을 때에만 프로세스 종료 뒤에도 이전 효과가 남는 사례를 논의합니다. NUMA는 한 시스템의 공유 물리 메모리 접근 위치와 비용 문제이고, 원격 호출은 별도 주소 공간·직렬화·전송·부분 장애를 포함합니다. 두 비용을 하나의 메모리 모델로 단순화하지 않습니다.
같은 컴퓨터에서는 자원 배분·메모리·동기화를, 여러 노드에서는 메시지와 실패 상태까지 함께 추적합니다. 분산이라는 이름보다 자원과 장애의 경계를 찾는 것이 출발점입니다.