Tokio `mpsc` 채널이 예상보다 큰 메모리를 할당하는 이유와, 이것이 실제 Rust 애플리케이션 성능에 미치는 영향을 살펴봅니다.
2026년 5월 12일·3분
최근 저는 Rust 리버스 프록시인 agentgateway의 메모리 사용량을 분석하고 최적화하는 데 많은 시간을 쓰고 있습니다. 그 과정에서 반복적으로 눈에 띈 한 가지는, 겉보기에는 무해해 보이는 Tokio mpsc 채널에 놀랄 만큼 많은 메모리가 할당된다는 점이었습니다.
제 순진한 이해로는, 다음과 같은 할당 패턴을 예상했을 것입니다:
struct BigStruct {
data: [u8; 1024],
}
fn main() {
// 약 1024바이트 할당
let _ = tokio::sync::mpsc::channel::<BigStruct>(1);
// 약 1024*1024바이트 할당
let _ = tokio::sync::mpsc::channel::<BigStruct>(1024);
}
하지만 실제로는 둘 다 틀렸습니다. 각각 32kb를 할당합니다!
우리 애플리케이션에서는 이것이 꽤 의미 있는 성능 영향을 준 두 가지 특정 영역이 있었습니다.
이를 조금 더 파고들기 위해 저는 mpsc의 할당을 분석하는 작은 playground를 만들었습니다.
결과는 꽤 흥미로웠습니다:
msg_size | capacity | heap_after_create | heap_after_fill |
|---|---|---|---|
| 8 | 1 | 800 B | 800 B |
| 8 | 16 | 800 B | 800 B |
| 8 | 1024 | 800 B | 9728 B |
| 128 | 1 | 4640 B | 4640 B |
| 128 | 16 | 4640 B | 4640 B |
| 128 | 1024 | 4640 B | 132608 B |
| 1024 | 1 | 33312 B | 33312 B |
| 1024 | 16 | 33312 B | 33312 B |
| 1024 | 1024 | 33312 B | 1050112 B |
여기서 각 항목은 다음을 뜻합니다:
msg_size: 구조체 T의 크기capacity: bounded mpsc 채널의 용량heap_after_create: 생성 직후 할당된 메모리heap_after_fill: 채널을 가득 채우기 위해 capacity 개의 항목을 보낸 직후 할당된 메모리여기서 몇 가지가 눈에 띕니다:
(msg_size * 32) + 544입니다. 즉, 고정 비용과 메시지 크기에 따른 배수 비용이 있습니다.이 32 배수는 Tokio 소스를 보면 쉽게 찾을 수 있습니다.
이 채널은 Block의 연결 리스트로 구성됩니다. 각 Block은 BLOCK_CAP``T 값을 저장하며, BLOCK_CAP은 32로 하드코딩되어 있습니다.
이 때문에 mpsc를 할당하면 대략 [T; 32]를 할당하게 됩니다.
나머지 고정 544바이트는 채널의 다른 부분에서 오는데, 저는 그 부분은 아주 자세히 분석하지는 않았습니다.
첫 번째 실제 영향은 클러스터의 각 Kubernetes Service 객체마다 하나씩 만든 채널이었습니다. 이 채널은 서비스 상태에 대한 이벤트를 보내는 데 사용했습니다. 많은 환경에서는 이런 객체 수가 수천 개에 이릅니다.
각 채널로 보내는 메시지는 작고(24바이트뿐입니다), 처리량과 지연 시간 요구사항도 매우 낮습니다.
하지만 위에서 본 것처럼 각 채널은 1312바이트를 차지하고 있었습니다! 우리는 이것을 한 번에 T 하나만 할당하는 다른 채널인 futures-channel로 옮겼습니다(그 대가로 처리량 저하가 있었지만, 우리에게는 중요하지 않았습니다). 그 결과 대표적인 테스트에서 전체 메모리 사용량을 절반으로 줄일 수 있었습니다:

최적화 전후 Agentgateway 메모리
Hyper를 사용할 때 연결당 16kb 할당이 발생하는 것을 볼 수 있습니다. 수천 개의 연결을 처리하면 이것도 꽤 크게 누적될 수 있습니다.
그중 일부는 명확합니다. 각 연결에는 하드코딩된 버퍼 INIT_BUFFER_SIZE: usize = 8192가 있습니다.
하지만 나머지 8kb도 같은 mpsc 문제에서 옵니다!
요청을 보내는 API인 각 SendRequest는 http::Request를 전달하는 채널을 사용합니다. 각 요청은 보통 약 250바이트 정도이므로, 여기에 32를 곱하면 남은 8kb가 나옵니다.
이전 사용 사례와 달리, 이 코드 경로는 지연 시간과 처리량에 매우 민감합니다. 선행 할당은 종단 간 벤치마크에 의미 있는 영향을 주지만, 실제로는 채널에 동시에 1~2개 항목만 있으면 충분합니다. 32 블록 크기는 거의 순수한 오버헤드에 가깝습니다.
이 내용은 Hyper 이슈 #4057에서 추적되고 있습니다.
다음 »더 나은 Kubernetes CRD 문서© 2026 howardjohn's blog · Powered by Hugo&PaperMod