거대한 인프라를 운영하는 조직에서 자원 절감은 대체로 1%씩 깎아내는 지루한 싸움이다. 그러나 때로는 알고리즘 하나에 손을 대는 것만으로 판이 크게 바뀐다. Cloudflare는 자사 Pingora 기반 서비스의 메모리 사용량을 줄여 전 세계적으로 100TB가 넘는 RAM을 회수했다. 이는 앞서 DNS 팀이 별도로 확보한 100TB에 더해진 성과다. 수천 대의 서버와 페타바이트급 메모리를 굴리는 규모에서는 작은 개선도 크게 증폭되기 때문에, 단일 알고리즘의 미세 조정이 이 정도 결과로 이어질 수 있다.
발단은 성능 팀의 티켓이었다. 내부 로드 밸런싱 서비스인 Pingora Backend Router(PBR)가 예상보다 훨씬 많은 메모리를 쓰고 있었고, 그 원인은 오픈소스 일관된 해싱 라이브러리인 pingora-ketama와 관련된 자료구조에 몰려 있었다. 문제를 이해하려면 일관된 해싱이 무엇이고 왜 이렇게 메모리를 많이 먹게 되었는지부터 짚어야 한다.
일관된 해싱과 가상 노드의 비용
일관된 해싱은 서버가 추가되거나 제거될 때 재배치를 최소화하면서 작업을 여러 서버에 분산하는 기법이다. Cloudflare는 캐시 가능한 요청을 URL 기준으로 서버에 라우팅하는 데 이를 사용한다. 덕분에 데이터센터마다 파일 사본을 하나만 두고도 각 파일의 위치를 안정적으로 찾을 수 있다. 핵심은 해시 함수의 출력이 결국 하나의 부호 없는 정수(32·64·128비트)로 제한된다는 점이다. 서버와 작업을 같은 수직선 위에 올려놓고, 각 작업의 바로 왼쪽에 있는 서버에 배정하는 방식으로 매핑이 이뤄진다.
문제는 해시가 사실상 난수라 서버마다 담당 구간의 크기가 들쭉날쭉하다는 데 있다. 담당 구간의 크기는 그 서버가 처리하는 요청 비율과 비례하므로, 편차가 크면 부하가 한쪽으로 쏠린다. 이 불균형을 통계적으로 다루기 위해 기댓값과 표준편차, 그리고 이를 기댓값으로 나눈 변동계수를 본다. 서버가 100대일 때 각 서버는 평균적으로 전체의 약 0.99%를 담당하지만, 해시가 하나뿐이면 변동계수는 99%에 이른다.
이를 완화하는 방법이 서버당 여러 개의 해시(가상 노드)를 두는 것이다. 짧은 구간과 긴 구간이 서로 상쇄되면서 분포가 고르게 다듬어지는데, 이는 큰 수의 법칙이 말하는 바와 같다. NGINX는 서버당 해시 수를 160으로 하드코딩했고 Pingora도 같은 기본값을 쓴다. 100대 예시에서 서버당 160개 해시를 쓰면 변동계수가 99%에서 약 8%로 떨어진다. 또한 ketama 알고리즘은 가중치를 지원해, Cloudflare처럼 저장 용량이 다른 서버에는 디스크 공간을, 연산 위주 작업에는 CPU나 GPU 수를 기준으로 요청을 배분할 수 있다. 여기에 규정 준수나 활성화된 캐싱 기능 때문에 특정 요청을 처리할 수 있는 서버가 제한되면, 기능 조합마다 별도의 링이 필요해져 메모리 부담은 더 커진다.
정렬 규칙을 우회한 25% 절감
첫 번째 큰 개선은 해시 저장 구조체에 대한 통찰에서 나왔다. 인덱스의 크기를 줄여도 메모리는 줄지 않았는데, Rust의 정렬 규칙 때문이다. 구조체 크기는 가장 큰 필드의 배수여야 하고, 4바이트인 해시가 가장 크므로 Point 하나의 최소 크기는 8바이트로 고정된다. 논란이 있는 #[repr(packed)] 대신, 해시와 인덱스를 원시 바이트 배열로 저장하고 게터로 접근하는 더 안전한 방식을 택했다. 두 방법은 컴파일 결과가 같지만 후자가 안전하다. 이 단순한 변경만으로 일관된 해싱에 쓰이는 메모리가 25% 줄었다.
더 큰 절감은 다시 수학에서 나왔다. 서버당 해시가 여러 개일 때의 표준편차 공식은 대부분의 자료가 근사치나 점근적 한계만 제시하지만, Cloudflare는 정확한 값을 도출했다. 여기에 현실적 제약이 하나 더 있다. 실제로는 32비트 해시를 쓰기 때문에 충돌 가능성이 있고, 생일 역설처럼 해시 수가 늘수록 충돌 확률이 빠르게 증가한다. 충돌이 나면 일부 해시의 기여가 무작위로 사라지면서 예측 불가능한 오차가 생긴다. 시뮬레이션 결과, 서버가 2048대인 데이터센터에서는 서버당 해시가 1만~10만 개 구간에서 오히려 오차율이 올라갔다. 역설적이지만 이 발견은 절감 계획에 힘을 실어줬다. 수학적 근거를 확보한 덕분에 서버당 생성하는 해시 수를 90%까지 줄여도 유의미한 오차가 발생하지 않는다는 결론에 도달했다.
한 번에 뒤집지 않은 이유
다만 해시 링을 바꾸면 일부 캐시 요청의 목적지가 달라진다. 새 링이 더 낫더라도 전체 네트워크를 한꺼번에 전환하면 캐시된 콘텐츠가 사실상 대부분 무효화되고, 메모리 최적화가 오리진 트래픽 폭증이라는 재앙으로 바뀐다. 그래서 Cloudflare는 일괄 전환 대신 PBR이 옛 ketama 링과 새로운 작은 링을 한동안 함께 메모리에 유지하도록 했다. 각 요청은 기존 마이그레이션 프레임워크를 통해 어느 링으로 백엔드를 고를지 결정했고, 그 판단은 요청 해시 기준으로 안정적이었다. 문제가 보이면 재배포 없이 새 요청을 옛 링으로 되돌릴 수 있는 깔끔한 롤백 경로도 확보됐다.
이 사례가 실무자에게 주는 함의는 분명하다. 규모가 큰 시스템에서는 관행적으로 굳어진 기본값(여기서는 서버당 160개 해시)이 정말 필요한지 통계적으로 검증할 가치가 있으며, 언어의 메모리 정렬 규칙 같은 저수준 특성이 대규모에서는 무시할 수 없는 비용으로 돌아온다는 점이다. 동시에 캐시 라우팅처럼 상태에 민감한 변경은 아무리 개선이라도 점진적 전환과 롤백 경로 없이는 위험하다. 절감의 크기만큼이나, 그것을 사고 없이 적용하는 방법이 이 이야기의 핵심이다.