네트워크와 인터넷 4강. 서버가 여러 대가 되면 생기는 일
서버 한 대에서는 사고였던 것이 서버 백 대에서는 일상입니다. 분산 시스템은 고장을 없애는 대신 고장을 전제로 설계합니다.
풀고 시작
더 큰 기계를 사는 방법의 한계
서비스가 잘되면 서버가 버거워집니다. 첫 대응은 단순하죠. CPU와 메모리, 디스크를 더 좋은 것으로 바꾸는 수직 확장(scale up)입니다. 코드를 거의 고치지 않아도 되니 손쉽지만 두 가지 벽에 부딪힙니다. 하나는 성능당 가격입니다. 최상급 부품으로 갈수록 두 배의 성능에 훨씬 큰 비용이 붙죠. 다른 하나는 더 근본적입니다. 아무리 좋은 기계도 한 대는 한 대여서, 전원이 나가거나 부품이 타면 서비스 전체가 멈추는 단일 장애점이 됩니다.
그래서 수평 확장(scale out)으로 갑니다. 평범한 서버를 여러 대 두고 요청을 나눠 처리하면 늘리는 데 한계가 없고 한 대가 죽어도 나머지가 받아 주죠. 다만 여기서부터 문제의 성격이 바뀝니다. 한 대 안에서는 곧바로 참이던 것이 여러 대 사이에서는 시간이 지나야 참이 되기 때문이죠.
복제하는 순간 진실이 여러 개가 됩니다
데이터를 여러 서버에 복제(replication)하면 읽기를 나눠 처리할 수 있고 한 대가 망가져도 데이터가 남습니다. 그런데 잃는 것도 있죠. 값이 바뀌어도 모든 복제본에 반영되기까지 시간이 걸리고, 그사이 다른 복제본을 읽은 사람은 옛날 값을 봅니다. 방금 올린 글이 새로고침하면 사라졌다 다시 나타나는 일이 그것이죠.
해결책은 있지만 공짜가 아닙니다. 모든 복제본이 기록을 끝낼 때까지 기다리는 동기 복제를 쓰면 값은 항상 일치하지만, 응답이 가장 느린 복제본에 묶이고 한 대만 멎어도 전체가 멈춥니다. 반대로 일단 응답하고 나중에 전파하는 비동기 복제는 빠르지만 그 틈에 값이 어긋나죠. 여기서 일관성과 가용성의 줄다리기가 시작됩니다.
CAP 정리를 오해하지 맙시다
이 줄다리기를 정리한 것이 CAP 정리입니다. 2000년 에릭 브루어(Eric Brewer)가 추측으로 냈고 2002년 길버트와 린치가 증명했죠. 세 글자는 모든 읽기가 최신 값을 본다는 일관성, 정상 노드는 모든 요청에 응답한다는 가용성, 메시지가 끊겨도 시스템이 계속 동작한다는 분단 내성(partition tolerance)을 가리킵니다.
그리고 가장 널리 오해되는 문장이 이어집니다. "셋 중 둘을 고르라." 이 요약은 틀렸습니다. 분단은 고르는 항목이 아니라 그냥 일어나는 일이기 때문이죠. 케이블은 끊어지고 스위치는 고장 나며, 흩어진 이상 이 사건을 거부할 방법이 없습니다. 정리의 정확한 의미는 이렇습니다. 네트워크가 분단된 그 순간에, 한쪽의 응답을 거절해 일관성을 지킬지 아니면 옛 값일 수도 있는 답을 주더라도 응답할지 중 하나를 골라야 한다. 분단이 아닌 평상시에는 둘을 함께 누릴 수 있습니다. 다니엘 아바디의 PACELC 확장은 여기에 한 줄을 더합니다. 분단이 없을 때는 일관성과 지연 시간의 교환이라는 것이죠.
언젠가는 같아진다는 약속
가용성을 고른 시스템이 내놓는 보장이 최종 일관성(eventual consistency)입니다. 새 쓰기가 멈추면 복제본들이 결국 같은 값으로 수렴한다는 약속인데, 결정적인 것은 언제까지인지를 말하지 않는다는 점이죠. 좋아요 숫자가 몇 초간 다르게 보여도 괜찮으니 이 정도로 충분한 곳이 많습니다. 반대로 계좌 잔고나 좌석 예약처럼 두 번 쓰이면 안 되는 일에는 부족하죠.
강한 보장이 필요한 곳에서는 노드들이 한 값에 합의해야 합니다. 그래서 합의 알고리즘(consensus algorithm)이 등장하죠. 팩소스(Paxos)와 이를 이해하기 쉽게 재설계한 래프트(Raft)가 대표적이고, 핵심 발상은 과반수입니다. 과반이 동의한 결정만 확정하면 어떤 두 결정도 최소 한 노드를 공유하므로 모순될 수 없습니다. 분단이 나도 과반을 쥔 쪽만 진행하니 두 개의 진실이 생기지 않죠. 대가는 소수 쪽이 응답을 멈춘다는 것입니다. CAP에서 일관성을 골랐다는 말이 실제로는 이 뜻이죠. 참고로 완전 비동기 환경에서는 노드 하나만 죽어도 결정적 합의가 불가능하다는 FLP 결과가 1985년에 나와 있고, 실제 시스템은 시간 제한 가정으로 이 벽을 우회합니다.
고장은 예외가 아니라 상수입니다
서버 한 대의 고장이 몇 년에 한 번이라도 수천 대를 돌리면 매일 어딘가는 고장 납니다. 그래서 설계 전제가 뒤집히죠. 부분 실패는 예외가 아니라 항상 진행 중인 정상 상태입니다. 이 전제에서 실무 규칙이 나옵니다. 응답을 무한정 기다리지 않고 시간 제한을 두며, 재시도해도 결과가 같도록 요청에 멱등성을 부여하고, 계속 실패하는 상대에게는 요청을 잠시 끊어 연쇄 붕괴를 막습니다. 넷플릭스가 운영 중인 서버를 일부러 죽이는 도구를 돌린 것도 같은 발상이죠. 반드시 일어날 일이라면 아무도 없을 때보다 지켜보는 낮에 터지는 편이 낫죠.
클라우드는 이 전제를 상품으로 만든 것입니다. 남의 컴퓨터를 빌리는 일로만 보면 절반만 본 셈이죠. 진짜 변화는 서버가 API 호출로 몇 초 만에 생기고 사라지며, 고장 난 것을 고치는 대신 버리고 새로 만드는 운영이 가능해진 데 있습니다. 컨테이너는 여기에 맞는 포장입니다. 애플리케이션과 필요한 라이브러리를 한 덩어리로 묶되, 운영체제 전체를 얹는 가상 머신과 달리 호스트 커널을 공유해 가볍게 실행하죠. 어디서 실행하든 같은 환경이 재현되니 내 컴퓨터에서는 되던데 하는 말이 줄죠. 쿠버네티스 같은 오케스트레이터는 컨테이너가 정해진 개수만큼 살아 있도록 감시하고, 죽으면 다시 띄우고, 부하에 따라 수를 조절합니다. 사람이 지키던 규칙을 시스템이 대신 지키는 셈이죠. 다음 과목에서는 흩어진 시스템이 다루는 데이터 자체를 들여다봅니다.
인출 문제
생각해볼 질문 (정답 없음)
- 최종 일관성은 언젠가 같아진다고만 약속할 뿐 시점은 말하지 않습니다. 이 약속이 충분한 서비스와 부족한 서비스의 경계는 어디일까요.
이전: 네트워크와 인터넷 3강 · 다음: 데이터의 시대 1강