🚀 2026 스타트업 컨퍼런스

단순히 `.clone()` 복사해서 쓰나요? 그 습관, 조만간 프로덕션 환경을 박살 낼 겁니다.

단순히 `.clone()` 복사해서 쓰나요? 그 습관, 조만간 프로덕션 환경을 박살 낼 겁니다.

Poooling·2026년 1월 6일·3

Rust의 .clone()을 남용하다가 겪은 프로덕션 장애 삽질기입니다. 스트림 리소스 복제 시 발생하는 데이터 유실 문제와 추상화 뒤에 숨은 위험성을 경고합니다.

스타트업 야생에서 구르다 지금의 네임드 기업으로 이직한 뒤, 가장 크게 달라진 점은 '안정성'에 대한 강박입니다. 예전에는 "일단 돌아가면 장땡"이라며 배포 버튼을 눌렀지만, 이제는 테스트 커버리지와 엣지 케이스를 집요하게 파고듭니다. 그런데 최근, 8년 차 개발자라는 타이틀이 부끄러울 정도로 호되게 당한 경험이 있습니다. 바로 Rust의 그 편리하다는 .clone() 때문이었습니다. 이 글을 읽는 여러분도 아마 "컴파일러가 소유권(Ownership) 문제로 불평하면 일단 .clone() 박고 보자"는 유혹에 시달리실 겁니다. 저도 그랬으니까요. 하지만 그 안일함이 어떻게 디버깅 지옥을 만드는지, 제가 겪은 뼈아픈 삽질기를 통해 이야기해 보려 합니다.

상황은 사내에서 사용할 GitHub 머지 봇(bors)을 프로덕션에 올리기 위해 막바지 안정화 작업을 하던 중 발생했습니다. sqlx로 실제 DB를 붙이고 wiremock으로 GitHub API를 모킹(Mocking)하여 통합 테스트를 돌리고 있었는데, 간헐적으로 테스트가 깨지는 현상이 발견되었습니다. 10번 돌리면 1~2번 실패하는, 개발자가 제일 싫어하는 '재현 불가능한 버그'였죠. 로그를 보니 모킹된 GitHub 서버가 패닉을 일으키고 있었습니다. 원인은 황당하게도 JSON 역직렬화(Deserialization) 실패였습니다. 분명 클라이언트 코드에서는 페이로드를 꽉 채워 보냈는데, 받는 서버 쪽에서는 "본문(Body)이 비어있다"고 아우성이었으니까요.

개발자라면 누구나 공감할 "내 코드는 완벽해, 라이브러리 버그 아니야?"라는 오만한 생각이 고개를 들었습니다. wiremock이나 octocrab(GitHub 클라이언트) 같은 의존성 라이브러리를 의심하기 시작했죠. 하지만 경험상 99%는 제 코드 문제라는 걸 알기에, 꾹 참고 디버거를 돌렸습니다. Rust의 비동기 런타임 위에서 외부 크레이트 내부까지 트레이싱하는 건 정말 고역이었습니다. 로컬 레지스트리의 소스 코드를 건드려가며 로그를 심었지만, 클라이언트 내부에서는 분명 데이터가 존재했습니다. 도대체 어디서 데이터가 증발한 걸까요?

결국 저는 개발자의 마지막 보루인 와이어샤크(Wireshark)를 켰습니다. localhost 트래픽을 덤프 떠서 TCP 패킷을 하나하나 뜯어보았죠. 그리고 충격적인 장면을 목격했습니다. 첫 번째 요청은 정상적으로 JSON 바디가 있었지만, 실패 후 재시도(Retry)하는 두 번째 요청에서는 Content-Length: 0, 즉 빈 껍데기만 날아가고 있었습니다. 범인은 바로 '재시도 로직'에 있었습니다.

문제의 핵심은 HTTP 요청 객체의 '복제' 방식에 있었습니다. octocrab 라이브러리는 요청이 실패하면 이를 재시도하기 위해 원본 요청 객체를 .clone() 하고 있었습니다. 그런데 이 요청 객체 내부적으로 HTTP 본문(Body)은 단순한 바이트 배열이 아니라 '스트림(Stream)' 형태로 관리됩니다. 스트림은 본질적으로 한 번 읽으면 사라지는(consumable) 자원입니다. Rust의 타입 시스템에서 이를 안전하게 복제하는 건 매우 까다로운 일이라, 당시 구현체는 .clone() 호출 시 스트림인 본문을 아예 드랍(drop)해버리는 방식으로 동작했던 것입니다. 컴파일러를 달래기 위해 무심코 구현된 Clone 트레이트가, 실제 런타임에서는 데이터를 유실시키는 시한폭탄이었던 셈이죠.

이 경험은 제게 기술적인 지식 이상의 깨달음을 주었습니다. 우리는 흔히 Rust의 엄격한 컴파일러가 모든 메모리 문제를 잡아준다고 믿습니다. 또, Cursor나 Copilot 같은 AI 도구가 소유권 오류를 해결해 줄 때 .clone()을 제안하면 별 의심 없이 수락하곤 합니다. 하지만 도구는 '메모리 안전성'만 보장할 뿐, 그게 '비즈니스 로직의 정합성'까지 보장하지는 않습니다. 편의를 위해 도입된 기능(Ergonomic cloning)이 내부 동작 원리(스트림의 특성)를 가려버릴 때, 우리는 더 큰 위험에 노출됩니다.

이 글을 읽는 주니어, 미들급 개발자분들께 꼭 드리고 싶은 말씀은 "추상화된 편의성 뒤에 숨은 비용을 의심하라"는 것입니다. 라이브러리가 제공하는 메서드, AI가 짜준 코드, 심지어 언어 차원에서 제공하는 기본 기능조차도 그 내부가 어떻게 동작하는지 모르면 언젠가 배신합니다. 특히 I/O나 네트워크처럼 상태를 가진 리소스를 다룰 때는 더더욱 조심해야 합니다. 저처럼 밤새 패킷 덤프를 뜨며 후회하지 않으려면, "이게 왜 되지?" 혹은 "이게 왜 안 되지?"라는 질문을 멈추지 마십시오. 그 집요함만이 여러분을 더 단단한 개발자로 만들어 줄 것입니다.

Poooling
PooolingAuthor

Poooling님의 다른 글

댓글 0

첫 번째 댓글을 남겨보세요!