Distributed System
목차
분산 시스템이 필요한 이유
분산 시스템은 여러 컴퓨터가 함께 하나의 시스템처럼 동작하는 구조다. 서버 한 대로 감당하기 어려운 트래픽, 저장 용량, 장애 대응을 해결하기 위해 시스템을 여러 노드로 나눈다.
1
2
3
4
5
client
-> load balancer
-> app node 1
-> app node 2
-> database cluster
서버를 여러 대로 늘리면 처리량과 장애 대응 능력을 높일 수 있다. 하지만 동시에 새로운 문제가 생긴다.
1
2
장점 더 많은 트래픽 처리, 장애 격리, 확장성
새 문제 네트워크 실패, 데이터 일관성, 배포 복잡도, 관측 난이도
즉 분산 시스템은 단순히 “서버를 많이 두는 것”이 아니다. 여러 노드가 실패할 수 있는 환경에서도 사용자가 하나의 안정적인 시스템처럼 느끼게 만드는 설계다.
노드와 클러스터
노드는 분산 시스템을 구성하는 개별 컴퓨터, VM, 컨테이너, 또는 프로세스를 의미한다. 클러스터는 여러 노드가 모여 하나의 목적을 위해 동작하는 집합이다.
1
2
3
4
cluster
├── node-1
├── node-2
└── node-3
노드가 많아지면 일부 노드가 죽어도 전체 시스템이 계속 동작할 수 있다. 하지만 노드 상태를 추적하고, 요청을 어디로 보낼지 결정하고, 데이터가 어느 노드에 있는지 관리해야 한다.
예를 들어 앱 서버 클러스터는 로드 밸런서를 통해 요청을 나눠 받는다.
1
2
3
4
5
browser
-> load balancer
-> app-1
-> app-2
-> app-3
이때 app-2가 죽으면 로드 밸런서는 app-2로 요청을 보내지 않아야 한다. 그래서 health check가 필요하다.
네트워크 실패
분산 시스템의 핵심 난점은 네트워크가 항상 믿을 수 없다는 점이다. 메시지는 늦게 도착할 수 있고, 중간에 사라질 수 있고, 중복될 수 있고, 순서가 바뀔 수 있다.
1
2
3
4
5
6
가능한 문제
-> 요청 timeout
-> 응답 유실
-> 중복 요청
-> 부분 장애
-> 네트워크 분리
장애를 예외로 보면 분산 시스템을 안정적으로 만들기 어렵다. 장애는 기본 조건으로 봐야 한다.
예를 들어 결제 요청을 보냈는데 응답이 timeout되었다고 하자.
1
2
client -> payment service: 결제 요청
client <- timeout
이때 실제로 결제가 실패했는지, 성공했지만 응답만 유실되었는지 알 수 없다. 그래서 재시도, idempotency key, 상태 조회 API 같은 설계가 필요하다.
1
idempotency = 같은 요청이 여러 번 와도 결과가 한 번 처리된 것처럼 만드는 성질
분산 환경에서 idempotency가 중요한 이유는 재시도가 필수인데, 재시도가 중복 처리로 이어지면 안 되기 때문이다.
복제와 샤딩
복제(replication)는 같은 데이터를 여러 노드에 복사해 두는 것이다. 목적은 가용성과 읽기 확장성이다.
1
2
3
primary database
-> replica-1
-> replica-2
복제를 하면 primary가 장애가 나도 replica를 승격하거나, 읽기 요청을 replica로 분산할 수 있다. 하지만 “방금 쓴 데이터가 replica에서 바로 보이는가?” 같은 일관성 문제가 생긴다.
샤딩(sharding)은 데이터를 여러 노드에 나누어 저장하는 것이다. 목적은 저장 용량과 쓰기 처리량 확장이다.
1
2
3
user_id 1~1000 -> shard-1
user_id 1001~2000 -> shard-2
user_id 2001~3000 -> shard-3
복제와 샤딩은 다르다.
1
2
replication 같은 데이터를 여러 곳에 복사
sharding 다른 데이터를 여러 곳에 분할
실제 시스템에서는 복제와 샤딩을 함께 사용하기도 한다.
1
2
shard-1 primary -> shard-1 replica
shard-2 primary -> shard-2 replica
일관성과 가용성
일관성(consistency)은 여러 노드가 같은 데이터를 같은 방식으로 보이게 하는 성질이다. 가용성(availability)은 시스템이 요청에 계속 응답할 수 있는 성질이다.
분산 시스템에서는 네트워크 분리(partition)가 발생할 수 있다. 이때 모든 노드가 서로 통신하지 못하는 상황에서도 일관성과 가용성을 동시에 완벽히 만족하기 어렵다.
1
2
3
네트워크 분리 발생
-> 모든 요청에 응답할 것인가?
-> 서로 다른 값이 생기지 않게 막을 것인가?
예를 들어 두 데이터센터가 서로 통신하지 못하는데 양쪽에서 같은 계좌 잔액을 수정할 수 있게 하면 값이 갈라질 수 있다. 반대로 한쪽 요청을 막으면 일관성은 지키지만 가용성은 떨어진다.
1
2
일관성 우선 일부 요청을 거절하더라도 데이터 충돌 방지
가용성 우선 요청을 계속 받지만 나중에 충돌 해결 필요
중요한 것은 정답이 하나가 아니라는 점이다. 결제와 재고처럼 정확성이 중요한 도메인은 일관성을 더 중시하고, 좋아요 수나 조회수처럼 약간 늦게 맞아도 되는 데이터는 가용성을 더 중시할 수 있다.
합의 알고리즘
합의 알고리즘은 여러 노드가 하나의 값이나 상태에 동의하도록 만드는 알고리즘이다. 분산 시스템에서는 일부 노드가 실패하거나 메시지가 늦어져도 중요한 결정을 일관되게 내려야 한다.
예를 들어 어떤 노드가 leader인지 정해야 한다고 하자.
1
2
3
4
node-1
node-2
node-3
-> 하나의 leader 선택
leader가 여러 개 생기면 같은 데이터를 서로 다르게 변경할 수 있다. 합의 알고리즘은 이런 상황을 막기 위해 quorum, term, log replication 같은 개념을 사용한다.
대표적으로 Raft, Paxos 같은 알고리즘이 있다. 처음 배울 때는 세부 수학보다 다음 목적을 먼저 잡으면 된다.
1
2
3
여러 노드가
실패 가능성이 있는 네트워크 위에서
하나의 순서와 상태에 동의하게 만드는 것
합의는 강한 일관성을 위해 중요하지만 비용이 있다. 여러 노드 간 통신이 필요하므로 latency가 증가하고, 네트워크 분리 상황에서는 일부 요청을 거절할 수 있다.
메시지 큐와 이벤트
메시지 큐는 생산자와 소비자를 직접 연결하지 않고 중간에 메시지를 저장해 전달하는 구조다.
1
2
3
producer
-> message queue
-> consumer
메시지 큐는 다음 문제를 완화한다.
1
2
3
4
서비스 간 결합도 감소
일시적 트래픽 흡수
비동기 처리
재시도와 장애 격리
예를 들어 주문 생성 후 이메일을 보내야 한다고 하자. 주문 API가 이메일 발송까지 직접 기다리면 느려지고, 이메일 서비스 장애가 주문 생성 장애로 번질 수 있다.
1
2
3
주문 생성
-> order-created event 발행
-> email consumer가 비동기로 처리
이벤트 기반 구조에서는 중복 메시지, 순서, 재시도, idempotency를 반드시 고려해야 한다. 메시지가 정확히 한 번만 처리된다고 가정하면 위험하다.
관측 가능성
관측 가능성(observability)은 시스템 내부에서 무슨 일이 일어나는지 외부 신호로 파악할 수 있는 능력이다. 분산 시스템에서는 요청이 여러 서비스를 지나가기 때문에 장애 위치를 찾기 어렵다.
기본 신호는 세 가지다.
1
2
3
metrics 숫자로 보는 상태
logs 사건 기록
traces 요청의 이동 경로
예를 들어 사용자가 “결제가 느리다”고 말했을 때, 관측 가능성이 부족하면 어디가 느린지 알기 어렵다.
1
2
3
4
API latency 증가
-> trace로 payment service 구간 확인
-> log로 에러 메시지 확인
-> metrics로 DB CPU 확인
모니터링, 로깅, 트레이싱이 필수인 이유는 분산 시스템의 장애가 한 서버 안에 머무르지 않기 때문이다. 요청 경로 전체를 볼 수 있어야 원인을 좁힐 수 있다.
좋은 관측성은 단순히 데이터를 많이 모으는 것이 아니라, 운영자가 중요한 질문에 빠르게 답할 수 있게 만드는 것이다.