Skip to content

필사 모드: 분산 시스템이 알려준 연애의 기술

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — 두 노드 사이에는 언제나 네트워크가 있다

분산 시스템이 어려운 이유는 정해져 있습니다. 네트워크는 믿을 수 없고, 메시지는 사라지거나 늦게 도착하고, 상대 노드가 살아 있는지조차 확실히 알 수 없습니다. 무엇보다 결정적인 것은, 내가 저쪽 노드의 내부 상태를 절대 직접 볼 수 없다는 사실입니다. 나는 오직 상대가 보내오는 메시지로만 상대를 추측합니다.

이 문장을 다시 읽어 보면, 이건 사람과 가까워지는 일에 대한 설명이기도 합니다. 두 사람은 각자 독립적으로 돌아가는 시스템이고, 그 사이에는 언제나 신뢰할 수 없는 채널이 놓여 있습니다. 표정, 말투, 답장 속도, 침묵. 우리는 이 불완전한 신호들로 상대의 마음이라는 관측 불가능한 상태를 추정하며 삽니다.

그래서 저는 분산 시스템을 오래 다루면서, 그 안의 해법들이 이상하게도 관계에 대한 조언처럼 들리기 시작했습니다. 완벽한 네트워크를 가정하는 시스템은 반드시 무너집니다. 완벽한 상대를 가정하는 관계도 마찬가지입니다. 두 세계의 답은 똑같습니다. 완벽함이 아니라 회복 탄력성, 그리고 명시적인 확인과 약간의 너그러움입니다. 이 글은 그 겹침을 아홉 가지로 나눠 풀어 봅니다.

재시도와 백오프 — 문자를 도배하지 않기

요청이 실패했을 때 무작정 다시 보내면 안 됩니다. 상대 서버가 과부하로 넘어진 상황이라면, 실패한 클라이언트들이 동시에 재시도를 퍼부으면서 서버를 완전히 죽여 버리는 재시도 폭풍(retry storm) 이 일어납니다. 그래서 잘 만든 시스템은 지수 백오프(exponential backoff) 를 씁니다. 첫 재시도는 1초 뒤, 다음은 2초, 그다음은 4초. 실패가 이어질수록 간격을 늘려 상대에게 회복할 틈을 줍니다. 여기에 무작위 지터(jitter)를 더해, 모두가 같은 순간에 몰려드는 것을 막습니다.

답장이 없을 때의 태도가 정확히 이것이어야 합니다. 읽고 답이 없다고 곧바로 "?", "자니?", "뭐 해?"를 연달아 쏘는 것은 재시도 폭풍입니다. 상대는 지금 회의 중이거나, 지쳤거나, 그냥 생각을 정리하는 중일 수 있습니다. 그럴 때 필요한 건 더 세게 두드리는 게 아니라, 간격을 늘리는 것입니다. 한 번 보냈으면 기다리고, 그다음엔 더 오래 기다립니다.

답이 없을수록 더 자주가 아니라 더 뜸하게. 백오프는 무관심이 아니라 상대의 회복을 위한 공간이다.

지터의 교훈도 있습니다. 매번 정확히 같은 시각에 같은 톤으로 연락하지 마세요. 예측 가능한 압박은 그 자체로 부담이 됩니다.

타임아웃 — 언제까지 기다릴지 미리 정해두기

타임아웃이 없는 요청은 시스템에서 가장 위험한 것 중 하나입니다. 응답을 무한정 기다리는 스레드는 자원을 붙든 채 영원히 멈춰 있고, 그런 스레드가 쌓이면 시스템 전체가 서서히 목이 졸립니다. 그래서 성숙한 시스템은 모든 외부 호출에 상한을 둡니다. "이 시간 안에 답이 없으면 실패로 간주하고 다음으로 넘어간다." 기다림에는 반드시 끝이 있어야 합니다.

관계에서도 무한대의 기다림은 멈춘 시스템입니다. "언젠가는 마음을 열겠지", "언젠가는 달라지겠지"를 기약 없이 기다리는 상태는, 자원을 붙든 채 아무 일도 처리하지 못하는 스레드와 같습니다. 그 사이 내 삶의 다른 요청들은 전부 대기열에 밀려 굶어 죽습니다.

타임아웃을 설정한다는 건 상대를 재촉하는 게 아닙니다. 나 자신에게 정직해지는 것입니다. 얼마나 기다릴지, 무엇을 확인하고 싶은지를 스스로 미리 정해두는 것. 그 시간이 지나도 신호가 없다면, 그건 상대를 미워할 이유가 아니라 내가 다음 상태로 넘어가도 된다는 조용한 허락입니다.

끝이 정해지지 않은 기다림은 헌신이 아니라 멈춤이다.

하트비트 — "오늘 하루 어땠어" 라는 작은 핑

분산 시스템의 노드들은 서로가 살아 있는지 알기 위해 주기적으로 작은 신호를 주고받습니다. 이걸 하트비트(heartbeat) 라고 합니다. 무거운 데이터가 아니라, "나 여기 있어"라는 가벼운 핑입니다. 하트비트가 몇 번 연속으로 오지 않으면, 상대 노드가 죽었다고 판단하고 대응을 시작합니다. 연결이 살아 있다는 확인은 큰 사건이 아니라 이 작은 신호의 꾸준함에서 옵니다.

관계도 정확히 그렇게 유지됩니다. 대단한 이벤트나 기념일이 관계를 지탱하는 게 아닙니다. "밥 먹었어?", "오늘 하루 어땠어?", "출근 잘했어?" 같은, 정보량은 거의 없지만 꾸준한 핑이 지탱합니다. 이 작은 신호들이 끊기기 시작할 때, 사실 연결은 이미 조용히 약해지고 있는 것입니다.

중요한 건 빈도이지 크기가 아닙니다. 하트비트는 원래 가벼워야 합니다. 매번 깊은 대화를 나눌 필요는 없습니다. 다만 규칙적이어야 합니다. 그리고 상대의 하트비트가 며칠째 오지 않는다면, 화를 내기 전에 먼저 알아채는 감도가 필요합니다. 시스템은 침묵을 장애의 신호로 읽습니다. 사람도 그렇습니다.

일관성 모델 — 우리, 지금 같은 페이지에 있나요

분산 시스템에서 강한 일관성(strong consistency) 은 모든 노드가 항상 같은 값을 보는 것을 뜻합니다. 누가 언제 읽어도 최신 상태가 보장됩니다. 대신 비쌉니다. 조율에 시간이 걸리고 가용성을 깎아먹습니다. 반대로 최종 일관성(eventual consistency) 은 잠깐의 불일치를 허용합니다. 지금은 노드마다 값이 다를 수 있지만, 새로운 갱신이 없다면 언젠가는 모두 같은 값으로 수렴합니다. 좋은 설계는 모든 데이터에 강한 일관성을 강요하지 않습니다. 어디에 강한 일관성이 꼭 필요한지를 고릅니다.

관계도 그렇습니다. 모든 것을 매 순간 완벽히 일치시키려 하면 서로가 지칩니다. "우리 지금 사귀는 거 맞아?", "우리 서로에게만 진지한 거 맞아?" 같은 것은 강한 일관성이 필요한 항목입니다. 여기서 최종 일관성에 맡겼다가는, 두 사람이 서로 다른 관계를 살고 있다는 사실을 뒤늦게 발견하고 크게 깨집니다. 이런 건 명시적으로 합의해 즉시 동기화해야 합니다.

반면 "이번 주말에 뭐 볼지", "어느 식당에 갈지" 같은 것은 최종 일관성으로 충분합니다. 지금 당장 완벽히 맞지 않아도, 대화를 몇 번 주고받으면 자연스럽게 수렴합니다. 여기까지 강한 일관성을 요구하면 관계가 회의로 가득 찹니다.

어떤 것은 지금 당장 같은 값이어야 하고, 어떤 것은 언젠가 같아지면 된다. 이 둘을 구분하는 것이 성숙함이다.

백프레셔 — 감당할 수 있는 만큼만 흘려보내기

빠른 생산자가 느린 소비자에게 데이터를 쏟아부으면, 소비자의 버퍼가 넘치고 결국 시스템이 무너집니다. 그래서 잘 설계된 파이프라인에는 백프레셔(backpressure) 가 있습니다. 소비자가 생산자에게 "지금은 이만큼만 받을 수 있어"라고 거꾸로 신호를 보내면, 생산자는 그 속도에 맞춰 흐름을 늦춥니다. 처리 능력은 상대가 정하고, 보내는 쪽이 거기에 맞추는 구조입니다.

사람에게도 처리 용량이 있습니다. 힘든 한 주를 보내는 사람에게 내 걱정거리, 계획, 서운함, 요구를 한꺼번에 쏟아부으면 상대의 버퍼는 넘칩니다. 아무리 옳은 말이라도, 받을 수 없는 순간에 밀어 넣으면 유실됩니다. 사랑이 부족해서가 아니라, 큐가 가득 차서입니다.

백프레셔의 핵심은 소비자에게 속도 조절 권한을 준다는 것입니다. 관계에서는 이렇게 나타납니다. "지금 이 얘기 할 여유 있어?"라고 먼저 묻는 것. 그리고 상대가 "오늘은 좀 벅차"라고 신호를 보낼 때, 그걸 거절이 아니라 흐름 제어로 읽는 것. 좋은 파트너는 상대의 용량을 관측하며 자기 전송 속도를 조절합니다. 사랑을 덜 주는 게 아니라, 상대가 받을 수 있는 리듬으로 주는 것입니다.

우아한 성능 저하 — 힘든 날엔 핵심 기능만

큰 서비스는 한 부분이 고장 났다고 전체를 내리지 않습니다. 추천 엔진이 죽으면 추천만 잠시 끄고 결제와 로그인 같은 핵심은 계속 돌립니다. 이것이 우아한 성능 저하(graceful degradation) 입니다. 모든 걸 잃는 대신, 가장 중요한 것을 지키기 위해 덜 중요한 기능을 의도적으로 내려놓는 전략입니다.

사람도 좋은 날만 사는 게 아닙니다. 아프고, 지치고, 마음이 무너지는 날이 있습니다. 그런 날 관계에 완전한 가동을 요구하는 건 비현실적입니다. 재치 있는 농담, 세심한 배려, 긴 대화 같은 부가 기능은 잠시 꺼질 수 있습니다. 그래도 핵심 서비스, 즉 "나는 네 편이다"라는 신호만 살아 있으면 관계는 그날을 버팁니다.

이건 나 자신에게도, 상대에게도 적용됩니다. 내가 힘든 날엔 완벽한 파트너가 되려 애쓰는 대신 핵심만 지키면 됩니다. 상대가 힘든 날엔, 평소만큼 세심하지 않다고 장애로 처리하지 않는 너그러움이 필요합니다. 지금 프런트엔드가 좀 투박할 뿐, 백엔드는 여전히 살아 있다는 걸 아는 것.

무너지는 날의 목표는 완벽한 가동이 아니라, 핵심 서비스를 살려두는 것이다.

멱등성 — 사과는 두 번 한다고 두 배가 되지 않는다

멱등성(idempotency) 은 같은 연산을 여러 번 적용해도 결과가 한 번 적용한 것과 같은 성질입니다. 결제 요청에 멱등성 키를 붙이면, 네트워크 오류로 같은 요청이 두 번 도착해도 청구는 한 번만 됩니다. 상태를 바꾸는 것은 첫 번째 적용이고, 그 뒤의 재전송은 상태를 더 바꾸지 않습니다.

사과가 정확히 그렇습니다. 진짜 사과는 한 번, 제대로 전달되었을 때 상태를 바꿉니다. 그런데 같은 사과를 열 번 반복하면 관계가 열 배로 회복될까요? 그렇지 않습니다. 진심 어린 사과 한 번이 이미 상태를 옮겨 놓았다면, 그 뒤의 반복은 아무것도 더 바꾸지 못합니다. 오히려 "내가 이렇게까지 사과했잖아"라는 반복은, 사과를 상대의 회복이 아니라 내 죄책감을 덜기 위한 요청으로 바꿔 버립니다. 그건 더 이상 멱등하지 않은, 부작용을 쌓는 재전송입니다.

반대의 함정도 있습니다. 상대가 이미 받아들여 상태가 바뀌었는데, 그 사실을 확인하지 못해 같은 사과를 계속 재전송하는 것. 시스템이라면 상대의 ACK를 신뢰하고 재전송을 멈춰야 합니다. 관계에서는 상대가 "괜찮아, 알아들었어"라고 응답하면 거기서 멈추는 것이 그 신뢰입니다.

미안하다고 두 번 말한다고 두 배로 미안한 게 아니다. 제대로 된 사과 한 번이 상태를 바꾸고, 그 뒤의 반복은 나를 위한 것이다.

두 장군 문제 — 그 말, 진짜로 전해졌을까

두 장군 문제(Two Generals' Problem) 는 분산 컴퓨팅에서 가장 유명한 불가능성 결과 중 하나입니다. 두 장군이 골짜기를 사이에 두고, 오직 적진을 통과하는 전령으로만 연락하며 공격 시각을 합의해야 합니다. 문제는, 어떤 메시지도 도중에 사라질 수 있다는 것입니다. 장군 A가 "새벽에 공격"이라고 보냅니다. 장군 B가 확인 답신을 보냅니다. 그런데 A는 그 답신이 도착했는지 모릅니다. 그래서 B는 A가 답신을 받았는지 확인받고 싶어 합니다. 이 확인의 확인은 끝없이 이어지고, 신뢰할 수 없는 채널에서는 완벽한 합의가 이론적으로 불가능합니다.

관계의 가장 조용한 오해가 정확히 여기서 태어납니다. 나는 분명히 마음을 전했다고 생각합니다. 그런데 그 메시지가 상대에게 도착했는지, 도착했더라도 내 의도대로 이해되었는지, 나는 100퍼센트 확신할 수 없습니다. "당연히 알겠지"는 검증되지 않은 가정입니다. 표현하지 않은 마음은 전송되지 않은 패킷이고, 전송된 마음조차 오해라는 손실을 겪을 수 있습니다.

이론적으로 완벽한 합의가 불가능하다면, 실무의 답은 무엇일까요. 시스템의 답과 같습니다. ACK를 명시적으로 주고받고, 중요한 것은 확인의 라운드를 한 번 더 도는 것입니다. "이거 이렇게 이해했는데 맞아?"라고 되묻는 것. 상대의 말을 내 언어로 되돌려 확인하는 것. 완벽한 확신에는 결코 닿을 수 없지만, 명시적인 확인을 한 라운드 더 돌수록 오해의 확률은 확실히 줄어듭니다.

상대가 알아들었을 거라는 가정은, 도착 확인 없이 보낸 메시지다. 중요한 말일수록 확인을 한 번 더 돌려라.

TTL이 붙은 캐시로서의 신뢰 — 갱신하지 않으면 만료된다

캐시는 비싼 조회를 매번 반복하지 않으려고 결과를 저장해 두는 장치입니다. 하지만 대개 TTL(Time To Live), 즉 유효 기간이 붙습니다. 그 시간이 지나면 캐시된 값은 만료되어 다시 원본에서 확인해야 합니다. TTL이 있는 이유는 단순합니다. 세상은 변하고, 오래된 캐시는 어느 순간 현실과 어긋나기 때문입니다.

신뢰가 정확히 이 캐시입니다. 우리는 매 순간 상대를 처음부터 검증하지 않습니다. 그동안 쌓인 경험을 바탕으로 "이 사람은 믿을 만하다"는 값을 캐시해 두고, 그 덕분에 매번 의심하는 비싼 연산을 건너뜁니다. 신뢰가 관계를 빠르고 가볍게 만드는 이유입니다. 다만 이 캐시에는 TTL이 있습니다. 아무런 갱신 없이 오래 방치된 신뢰는 조용히 낡아 갑니다. 그래서 신뢰는 꾸준한 상호작용으로 계속 새로 고쳐 줘야 합니다.

그리고 배신은 이 관점에서 캐시 무효화(cache invalidation) 입니다. 어긋나는 한 번의 사건이, 그동안 캐시해 둔 신뢰 값을 통째로 무효로 만듭니다. 무효화 이후에는 다시 비싼 조회의 시대로 돌아갑니다. 매번 원본을 다시 확인해야 하고, 캐시를 재구축하는 데는 처음 만들 때보다 훨씬 오래 걸립니다. 신뢰라는 캐시는 만들기는 느리고, 무효화는 한순간이며, 재구축은 가장 비쌉니다.

신뢰는 캐시된 값이지 영구 저장된 진리가 아니다. 갱신하지 않으면 만료되고, 배신은 그 캐시를 통째로 무효화한다.

마치며 — 완벽한 네트워크는 없다

지금까지의 아홉 가지는 서로 다른 이야기처럼 보이지만, 뿌리는 하나입니다. 분산 시스템이 어려운 이유와 관계가 어려운 이유가 정확히 같기 때문입니다. 나는 상대의 내부 상태를 직접 볼 수 없고, 우리 사이의 채널은 신뢰할 수 없으며, 상대는 내 뜻대로 움직이지 않는 독립적인 존재입니다. 이 조건 위에서 우리는 협력해야 합니다.

이 조건 위에서 완벽을 목표로 삼는 설계는 반드시 실패합니다. 메시지가 절대 유실되지 않는다고 믿는 프로토콜, 상대가 언제나 완벽히 이해한다고 믿는 관계. 둘 다 첫 번째 장애에서 무너집니다. 그래서 좋은 시스템과 좋은 관계는 같은 것을 목표로 삼습니다. 실패를 없애는 게 아니라 실패를 견디는 것. 즉 회복 탄력성, 명시적인 확인, 그리고 상대의 불완전함을 장애가 아닌 정상 상태로 받아들이는 너그러움입니다.

문자를 도배하는 대신 백오프하고, 무한히 기다리는 대신 타임아웃을 두고, 작은 하트비트를 꾸준히 보내고, 중요한 것은 강하게 합의하고, 상대의 용량에 맞춰 흘려보내고, 힘든 날엔 핵심만 지키고, 사과는 제대로 한 번 하고, 중요한 말은 확인을 한 라운드 더 돌리고, 신뢰라는 캐시를 부지런히 갱신하는 것. 이것은 좋은 엔지니어링이자, 아마도 좋은 사랑입니다.

참고 자료

현재 단락 (1/38)

분산 시스템이 어려운 이유는 정해져 있습니다. 네트워크는 믿을 수 없고, 메시지는 사라지거나 늦게 도착하고, 상대 노드가 살아 있는지조차 확실히 알 수 없습니다. 무엇보다 결정적인...

작성 글자: 0원문 글자: 5,928작성 단락: 0/38