rust

15 개의 포스트

신뢰할 수 있는 Rust Worker 만들기: wasm-bindgen에서의 패닉 및 중단 복구 (새 탭에서 열림)

Cloudflare Workers 환경에서 Rust로 작성된 WebAssembly(Wasm)는 예기치 못한 패닉(Panic)이나 중단(Abort)이 발생할 경우 런타임이 정의되지 않은 상태로 남아 동일한 인스턴스의 다른 요청까지 실패하게 만드는 '샌드박스 오염' 문제를 안고 있었습니다. 이를 해결하기 위해 Cloudflare는 `wasm-bindgen` 메인 프로젝트와 협력하여 Wasm 예외 처리 기능을 활용한 `panic=unwind` 지원과 중단 복구 메커니즘을 도입했습니다. 결과적으로 단일 요청의 실패가 전체 서비스 중단으로 이어지는 것을 방지하고, 상태 유지가 필요한 애플리케이션에서도 안정적인 오류 복구가 가능해졌습니다. ### 초기 대응 및 완화 전략 본격적인 기능 개선에 앞서, Cloudflare는 운영 환경에서의 피해를 최소화하기 위해 사용자 정의 패닉 핸들러와 JavaScript 프록시를 활용한 초기 완화책을 적용했습니다. * **패닉 핸들러 도입:** Rust 내부에 상태를 추적하는 패닉 핸들러를 설치하여 실패가 발생하면 전체 애플리케이션을 다시 초기화하도록 설정했습니다. * **Proxy 기반 간접 참조:** JavaScript 영역에서 Rust 호출 경계를 `Proxy`로 감싸 모든 진입점을 캡슐화하고, 실패 시 안전하게 Wasm 모듈을 재로드했습니다. * **한계점:** 이 방식은 서비스 가용성은 높였으나, 전체 애플리케이션을 재시작해야 하므로 메모리에 상태를 보관하는 Durable Objects 같은 서비스에서는 데이터 손실이 발생하는 단점이 있었습니다. ### Wasm 예외 처리를 통한 panic=unwind 구현 상태를 보존하면서 오류를 복구하기 위해 Wasm의 최신 표준인 예외 처리(Exception Handling) 제안을 활용하여 Rust의 패닉 언와인딩(Unwinding)을 구현했습니다. * **컴파일 옵션 변경:** `RUSTFLAGS='-Cpanic=unwind'`와 `-Zbuild-std`를 사용하여 표준 라이브러리가 언와인딩을 지원하도록 다시 빌드했습니다. * **wasm-bindgen 툴체인 업데이트:** Wasm 파서인 Walrus가 `try`, `catch`, `rethrow` 명령어를 인식하도록 수정하고, JavaScript와 Rust 경계에서 예외가 올바르게 전달되도록 개선했습니다. * **Boundary 처리:** Rust에서 발생한 패닉이 JavaScript의 `PanicError`로 변환되도록 했으며, `extern "C-unwind"`를 통해 함수 호출 경계를 넘나드는 언와인딩을 허용했습니다. * **클로저 안전성:** `MaybeUnwindSafe` 트레잇을 도입하여 언와인딩 시 안전하지 않은 참조를 캡처하는 클로저를 체크하고, 필요한 경우 패닉 시 즉시 중단되는 `Closure::new_aborting` 변형을 제공합니다. ### 중단(Abort) 복구 및 포이즌 필(Poison Pill) 메커니즘 메모리 부족(OOM)과 같이 언와인딩이 불가능한 '중단' 상황에서는 메모리 상태가 오염될 가능성이 높기 때문에, 해당 인스턴스의 재실행을 원천 봉쇄하는 전략을 사용합니다. * **포이즌 필 플래그:** `wasm-bindgen`은 이제 모든 Wasm 모듈에 내부 상태 플래그를 주입합니다. 만약 Rust 코드에서 중단이 발생하면 이 플래그가 즉시 설정됩니다. * **재실행 방지:** 이후 JavaScript에서 해당 Wasm 인스턴스의 어떤 함수라도 호출하려고 하면, Rust 코드를 실행하기 전에 플래그를 먼저 확인하여 즉시 JavaScript 에러를 발생시킵니다. * **안전성 보장:** 이를 통해 오염된 메모리 상태에서 코드가 다시 실행되어 발생할 수 있는 보안 취약점이나 예측 불가능한 동작을 완전히 차단합니다. Wasm 기반의 Rust 서비스를 운영한다면 최신 버전의 `wasm-bindgen`과 `workers` 라이브러리를 사용하고, 특히 Durable Objects와 같이 상태 보존이 중요한 경우 `panic=unwind` 설정을 활성화할 것을 권장합니다. 이는 단순한 안정성 향상을 넘어, Wasm이 네이티브 환경과 동등한 수준의 오류 복구 능력을 갖추게 되었음을 의미합니다.

Unweight: 품질 저하 없이 LLM을 22% 압축한 방법 (새 탭에서 열림)

Cloudflare는 LLM의 가중치를 15~22% 압축하면서도 출력 결과의 정확도를 비트 단위로 완벽하게 보존하는 무손실 압축 시스템인 'Unweight'를 공개했습니다. 이 시스템은 NVIDIA H100 GPU의 연산 능력에 비해 현저히 느린 메모리 대역폭 병목 현상을 해결하기 위해 설계되었으며, 추론 시 가중치를 고속 온칩 메모리(Shared Memory)에서 직접 해제하여 처리 효율을 극대화합니다. 결과적으로 Llama-3.1-8B 모델 기준 약 3GB의 VRAM을 절약함으로써, 품질 저하 없이 더 적은 자원으로 더 빠른 추론 서비스를 제공할 수 있게 되었습니다. ### 메모리 대역폭 병목 현상과 무손실 압축의 필요성 * **컴퓨팅-메모리 불균형:** NVIDIA H100의 텐서 코어는 메모리가 데이터를 전달하는 속도보다 약 600배 빠르게 데이터를 처리할 수 있어, 추론 속도의 핵심은 '메모리 버스를 통과하는 데이터양'을 줄이는 데 있습니다. * **양자화의 한계:** 4비트나 8비트 정수로 변환하는 기존 양자화 방식은 손실 압축(Lossy)이므로 모델의 응답 품질을 예측할 수 없게 만듭니다. * **무손실 아키텍처:** Unweight는 비트 단위로 동일한(Bit-exact) 출력을 보장하면서도 가중치 크기를 줄여, 서비스 품질을 타협하지 않고 하드웨어 효율성만 높였습니다. ### BF16 지수(Exponent) 데이터의 중복성 활용 * **데이터 구조 분석:** BF16 가중치는 부호(1비트), 지수(8비트), 가수(7비트)로 구성되는데, 이 중 부호와 가수는 무작위성이 강해 압축이 어렵지만 지수 부분은 매우 높은 중복성을 보입니다. * **지수 분포의 편향성:** 일반적인 LLM 레이어에서 가장 빈번하게 등장하는 상위 16개의 지수 값이 전체 가중치의 99% 이상을 차지한다는 점에 착안했습니다. * **허프만 코딩(Huffman Coding) 적용:** 정보 이론에 따라 빈도가 높은 지수에는 짧은 코드를, 낮은 지수에는 긴 코드를 할당하는 허프만 코딩을 통해 지수 스트림에서 약 30%의 압축률을 달성했습니다. ### GPU 온칩 메모리를 활용한 효율적 압축 해제 * **SMEM 직접 해제:** 압축된 가중치를 느린 메인 메모리(HBM)로 다시 돌려보내지 않고, 텐서 코어 바로 옆의 빠른 공유 메모리(SMEM)에서 즉시 해제하여 연산에 투입함으로써 추가적인 지연 시간을 방지합니다. * **선택적 적용:** 모델 파라미터의 약 2/3를 차지하며 메모리 트래픽의 주원인인 MLP(Multi-Layer Perceptron) 가중치 행렬에 집중적으로 적용하여 효율을 높였습니다. * **행 단위(Row-based) 최적화:** 64개 가중치로 구성된 한 행에 희귀 지수가 하나라도 포함되면 해당 행 전체를 무압축 상태로 저장하여, 커널 실행 시 복잡한 분기 처리를 줄이고 처리 속도를 최적화했습니다. ### 실용적인 결론 및 권장사항 Unweight는 모델의 정확도를 1%도 포기할 수 없으면서 VRAM 부족 문제를 해결해야 하는 고성능 추론 환경에 최적화된 솔루션입니다. 특히 NVIDIA Hopper 아키텍처(H100 등)를 사용하는 환경에서 Llama-3.1-8B와 같은 모델을 운용할 때 약 3GB의 메모리 여유 공간을 확보할 수 있어, 더 큰 배치 사이즈를 운용하거나 더 많은 모델을 하나의 GPU에 올리는 데 유용합니다. Cloudflare는 이 기술의 확산을 위해 기술 논문과 함께 GPU 커널을 오픈소스로 공개하였습니다.

Launching Cloudflare’s Gen 13 servers- trading cache for cores for 2x edge compute performance (새 탭에서 열림)

Cloudflare는 차세대 에지 컴퓨팅 성능을 2배로 끌어올리기 위해 AMD EPYC 5세대 'Turin' 프로세서를 기반으로 한 13세대(Gen 13) 서버를 도입했습니다. 기존 12세대 서버가 거대한 L3 캐시(3D V-Cache)에 의존했던 것과 달리, 13세대는 캐시 용량을 줄이는 대신 코어 수를 대폭 늘려 처리량을 극대화하는 전략을 선택했습니다. 이러한 하드웨어 변화는 Rust 기반의 새로운 요청 처리 계층인 'FL2'로의 전환이 있었기에 가능했으며, 이를 통해 캐시 의존성을 탈피하고 늘어난 코어 성능을 온전히 활용할 수 있게 되었습니다. ### AMD Turin 아키텍처의 혁신과 캐시 트레이드오프 AMD EPYC 5세대 Turin 프로세서는 단순한 코어 수 증설 이상의 아키텍처적 개선을 제공합니다. * **코어 밀도 및 효율성:** 12세대의 96코어에서 2배 늘어난 최대 192코어(384스레드)를 지원하며, Zen 5 아키텍처 적용으로 IPC(사이클당 명령어 처리 수)가 향상되었습니다. 코어당 전력 소모량은 오히려 32% 감소하여 전력 효율성이 개선되었습니다. * **메모리 대역폭 확장:** DDR5-6400 메모리를 지원하여 늘어난 코어들이 데이터를 신속하게 주고받을 수 있는 환경을 구축했습니다. * **캐시 감소의 한계:** 하지만 고밀도 설계를 위해 코어당 L3 캐시 용량은 12세대의 12MB에서 2MB로 크게 줄었습니다. 이는 캐시 로컬리티에 의존적인 기존 워크로드에 심각한 성능 병목을 일으킬 수 있는 구조적 변화입니다. ### 기존 FL1 스택에서의 성능 병목 분석 Cloudflare의 기존 NGINX 및 LuaJIT 기반 요청 처리 계층인 FL1은 줄어든 캐시 환경에서 심각한 지연 시간 문제를 노출했습니다. * **지연 시간 급증:** AMD uProf 도구 분석 결과, L3 캐시 미스 시 데이터 접근 시간이 50사이클에서 350사이클(DRAM 접근 시)로 7배 이상 증가하는 것을 확인했습니다. * **처리량과 지연 시간의 상충:** Turin 9965 프로세서에서 FL1을 실행했을 때 처리량(Throughput)은 62% 증가했지만, 높은 CPU 사용률 구간에서 지연 시간(Latency)이 50% 이상 늘어나는 결과가 나타나 실제 서비스 적용에 부적합 판정을 받았습니다. ### 하드웨어 튜닝 및 PQOS를 통한 최적화 실험 하드웨어의 한계를 극복하고 최적의 성능 지점을 찾기 위해 AMD와 협업하여 다양한 최적화 기술을 적용했습니다. * **하드웨어 튜닝:** 프리페처(Prefetcher) 및 데이터 패브릭(DF) 프로브 필터 조정 등을 시도했으나 성능 향상 폭은 미미했습니다. * **AMD PQOS 적용:** L3 캐시와 메모리 대역폭을 미세 조정할 수 있는 PQOS(Platform Quality of Service) 기술을 사용했습니다. * **NUMA 인지 구성:** 특정 CCD(Core Complex Die)를 FL 전용으로 할당하는 NUMA 인지형 코어 어피니티 설정을 통해, 지연 시간을 허용 범위 내로 유지하면서도 약 15%의 추가 처리량 이득을 확보하는 데 성공했습니다. ### Rust 기반 FL2 스택을 통한 성능 해방 결국 하드웨어의 잠재력을 100% 끌어올린 핵심 동력은 소프트웨어 재작성이었습니다. * **캐시 의존성 탈피:** Rust로 작성된 FL2는 효율적인 메모리 관리와 현대적인 설계를 통해 캐시 크기에 민감하게 반응하던 FL1의 한계를 극복했습니다. * **선형적 성능 확장:** FL2 도입을 통해 Turin 프로세서의 192코어 성능을 지연 시간 하락 없이 온전히 사용할 수 있게 되었으며, 이는 Cloudflare 에지 네트워크의 총 소유 비용(TCO) 최적화로 이어졌습니다. 인프라의 세대 교체 시 하드웨어 사양(코어 수, 캐시 용량 등)의 변화가 기존 소프트웨어 스택의 설계 원칙과 충돌할 수 있습니다. Cloudflare의 사례처럼 하드웨어 성능 최적화가 한계에 다다랐을 때는, Rust와 같은 현대적인 언어로 소프트웨어 아키텍처를 재설계함으로써 하드웨어의 물리적 변화를 성능 도약의 기회로 전환하는 전략이 필요합니다.

Gen 13 내부: 역대 가장 강력한 서버를 구축한 방법 (새 탭에서 열림)

Cloudflare는 Rust 기반의 새로운 요청 처리 계층인 FL2로의 전환에 맞춰, 하드웨어 성능과 효율성을 극대화한 'Gen 13' 서버를 설계했습니다. Gen 13은 192코어의 AMD EPYC Turin 프로세서와 향상된 메모리/네트워크 대역폭을 통해 이전 세대 대비 최대 2배의 처리량을 제공하면서도 전력 효율은 50% 개선했습니다. 결과적으로 하드웨어와 소프트웨어의 최적화된 결합을 통해 글로벌 네트워크 전반의 운영 비용을 절감하고 서비스 확장성을 확보하게 되었습니다. **소프트웨어 변화에 최적화된 CPU 선택** * **FL2와 L3 캐시 의존도 감소:** 이전 세대(Gen 12)는 대용량 L3 캐시가 특징인 Genoa-X를 사용했으나, Rust로 재작성된 FL2 스택은 L3 캐시 의존도가 낮아진 대신 코어 수에 따라 성능이 선형적으로 확장되는 특성을 보입니다. * **AMD EPYC 9965(Turin) 채택:** 코어당 캐시 용량은 줄었으나, 코어 수를 192개(Gen 12 대비 2배)로 늘려 총 처리량(Requests per second)을 극대화했습니다. * **전력 및 운영 효율성:** 500W TDP 설정에서 최적의 와트당 성능을 구현하며, 서버 한 대당 처리 능력을 높여 관리해야 할 노드 수를 줄임으로써 운영 복잡성을 낮췄습니다. * **미래 지향적 설계:** DDR5-6400, PCIe 5.0, CXL 2.0을 지원하며 AMD의 최신 아키텍처를 통해 더 긴 보안 지원 주기와 시스템 수명을 보장받습니다. **메모리 대역폭 및 용량의 극대화** * **12채널 구성:** AMD Turin 프로세서의 성능을 뒷받침하기 위해 12개의 메모리 채널을 모두 사용하는 '1 DIMM per channel(1DPC)' 구성을 채택했습니다. * **대역폭 33% 향상:** DDR5-6400 ECC RDIMM을 사용하여 초당 614GB의 최대 메모리 대역폭을 확보했으며, 이는 메모리 집약적인 병렬 작업 시 병목 현상을 방지합니다. * **용량 최적화:** 코어 수가 늘어남에 따라 전체 메모리 용량을 768GB로 증설하여, Cloudflare가 최적으로 판단하는 '코어당 4GB'의 메모리 비율을 유지했습니다. * **메모리 인터리빙:** 동일한 용량과 규격의 메모리를 12개 채널에 균등하게 배치하여 데이터 액세스 속도를 높이는 인터리빙 기술을 적용했습니다. **네트워크 및 스토리지 가속화** * **4배 더 빠른 네트워크:** 기존 25GbE에서 듀얼 100GbE NIC(네트워크 인터페이스 카드)로 전환하여 폭발적인 데이터 유입에도 지연 시간(SLA) 내에 처리가 가능하도록 설계했습니다. * **PCIe 5.0 기반 스토리지:** 24TB의 PCIe 5.0 NVMe 스토리지를 탑재하여 데이터 입출력 속도를 개선하고 용량을 1.5배 늘렸습니다. * **보안 강화:** 메모리 암호화뿐만 아니라 PCIe 암호화 하드웨어 지원을 추가하여 데이터 이동 시 보안성을 강화했습니다. Gen 13 서버는 단순한 사양 업그레이드를 넘어, 소프트웨어 아키텍처(Rust FL2)의 변화가 하드웨어 설계의 방향을 어떻게 바꿀 수 있는지 보여주는 사례입니다. 고밀도 컴퓨팅이 필요한 환경이라면 대용량 캐시에 의존하기보다, 최신 아키텍처 기반의 다코어 CPU와 이를 뒷받침할 수 있는 충분한 메모리 대역폭 및 네트워크 속도를 확보하는 것이 성능과 비용 효율성 측면에서 유리할 것입니다.

Amazon S3 20주년과 다음 단계 구축 | Amazon Web Services (새 탭에서 열림)

Amazon S3는 2006년 출시 이후 20년 동안 단순한 객체 스토리지를 넘어 전 세계 데이터 및 AI 워크로드의 핵심적인 보편적 기반으로 진화했습니다. 기술적 혁신을 통해 11나인(99.999999999%)의 내구성과 완벽한 하위 호환성을 유지하면서도, 비용을 85% 절감하고 엑사바이트 단위의 확장을 실현하며 클라우드 인프라의 표준을 제시하고 있습니다. **비약적인 규모의 확장과 경제성 확보** * 2006년 당시 1PB 수준이었던 총 용량은 현재 500조 개 이상의 객체와 수백 엑사바이트의 데이터를 수용하는 규모로 성장했습니다. * 최대 객체 크기는 5GB에서 50TB로 1만 배 증가했으며, 초당 요청 수는 전 세계적으로 2억 건을 상회합니다. * 기가바이트당 비용은 출시 초기 15센트에서 현재 약 2센트로 85% 감소했으며, 'S3 Intelligent-Tiering'을 통해 고객들은 표준 대비 60억 달러 이상의 비용을 절감했습니다. * S3 API는 업계 표준이 되어 수많은 벤더가 이를 채택하고 있으며, 2006년에 작성된 코드가 수정 없이 오늘날에도 그대로 동작할 만큼 엄격한 하위 호환성을 보장합니다. **규모의 한계를 극복하는 엔지니어링 혁신** * **지속적 데이터 감사:** 마이크로서비스 기반의 감사(Auditor) 시스템이 모든 바이트를 실시간으로 검사하며, 열화 징후가 발견되는 즉시 자동 복구 시스템을 가동하여 데이터 손실을 방지합니다. * **수학적 정확성 증명:** 인덱스 하위 시스템과 액세스 정책 등에 정형 기법(Formal methods)과 자동 추론을 적용하여 시스템의 일관성과 정확성을 수학적으로 증명합니다. * **Rust 언어 전환:** 성능에 민감한 요청 경로와 디스크 스토리지 코드를 Rust로 재작성하여 메모리 안전성을 확보하고, 대규모 운영 환경에서 발생할 수 있는 버그를 컴파일 단계에서 제거했습니다. * **규모의 경제 활용:** "규모가 곧 장점"이라는 철학 아래 시스템이 커질수록 개별 워크로드 간의 상관관계가 낮아지도록 설계하여 전체적인 안정성을 높였습니다. **데이터와 AI를 위한 미래 지향적 기능** * **S3 Tables:** Apache Iceberg 테이블을 완전 관리형으로 제공하며, 자동화된 유지보수를 통해 쿼리 효율을 높이고 스토리지 비용을 최적화합니다. * **S3 Vectors:** RAG(검색 증강 생성) 및 시맨틱 검색을 위해 최대 20억 개의 벡터를 인덱싱하며, 100ms 미만의 낮은 지연 시간으로 네이티브 벡터 검색을 지원합니다. * **S3 Metadata:** 대규모 버킷을 일일이 나열(List)하지 않고도 중앙 집중식 메타데이터를 통해 즉각적으로 데이터를 발견할 수 있어 데이터 레이크 분석 시간을 획기적으로 단축합니다. **권장 사항** S3는 이제 데이터를 저장만 하는 공간이 아니라, 데이터를 이동시키지 않고도 직접 분석하고 AI 모델에 활용할 수 있는 통합 플랫폼입니다. 비용 효율성을 극대화하기 위해 'Intelligent-Tiering'을 기본적으로 활용하고, 복잡한 데이터 파이프라인 대신 'S3 Tables'나 'S3 Metadata' 같은 최신 기능을 도입하여 데이터 관리의 복잡성을 줄이는 전략이 필요합니다.

ecdysis를 통한 오래된 코드 탈 (새 탭에서 열림)

Cloudflare는 수년간 자사 인프라에서 수백만 건의 요청을 중단 없이 처리하며 검증한 Rust 라이브러리 'ecdysis'를 오픈소스로 공개했습니다. 이 라이브러리는 네트워크 서비스 업데이트 시 연결 끊김이나 새로운 연결 거부 없이 프로세스를 재시작할 수 있는 '우아한 재시작(Graceful Restart)' 기능을 제공합니다. 이를 통해 보안 패치나 기능 업데이트 시에도 실시간 트래픽에 영향을 주지 않고 안전하게 최신 코드로 교체할 수 있습니다. ### 기존 재시작 방식의 한계와 문제점 * 단순한 재시작 방식(이전 프로세스 종료 후 새 프로세스 시작)은 소켓을 닫는 순간부터 새 프로세스가 리스닝을 시작할 때까지 공백이 발생하며, 이 기간에 들어오는 연결은 커널에 의해 `ECONNREFUSED`로 거부됩니다. * 이미 연결된 세션(대용량 업로드, 비디오 스트리밍, WebSocket 등)이 프로세스 종료와 함께 강제로 끊기며 사용자 경험에 악영향을 미칩니다. * `SO_REUSEPORT` 옵션은 여러 프로세스가 동일한 포트를 바인딩하게 해주지만, 새 프로세스가 연결을 수락(`accept`)하기 전에 이전 프로세스가 종료되면 커널 큐에서 대기 중이던 연결들이 고아 상태가 되어 폐기되는 고유의 결함이 있습니다. ### ecdysis의 작동 원리와 포크 모델 * NGINX의 설계 방식을 차용하여, 실행 중인 부모 프로세스가 `fork()`를 통해 자식 프로세스를 생성하고, 자식은 `execve()`를 실행하여 새 버전의 코드로 자신을 교체합니다. * 이 과정에서 부모 프로세스는 명명된 파이프(named pipe)를 통해 소켓 파일 디스크립터(FD)를 자식에게 상속하며, 두 프로세스가 잠시 소켓을 공유하여 공백 없는 트래픽 처리를 보장합니다. * 자식 프로세스가 초기화를 완료했다는 신호를 보내면 부모는 그제야 소켓을 닫고 기존 연결만 처리한 뒤 종료(Draining)되며, 만약 자식이 초기화 중 충돌하더라도 부모가 여전히 동작 중이므로 서비스 중단이 발생하지 않습니다. ### 주요 기능 및 시스템 통합 * **Tokio 비동기 런타임 지원**: 고성능 Rust 서비스를 위해 Tokio용 비동기 스트림 래퍼를 기본 제공하므로, 상속받은 소켓을 별도의 복잡한 연동 없이 즉시 리스너로 사용할 수 있습니다. * **systemd 통합**: `systemd-notify` 기능을 내장하여 서비스 유닛 설정의 `Type=notify-reload`와 연동될 수 있으며, 시스템 레벨에서 프로세스 수명 주기를 정확히 추적할 수 있습니다. * **검증된 신뢰성**: Cloudflare의 글로벌 네트워크에서 트래픽 라우팅, TLS 수명 주기 관리, 방화벽 규칙 적용 등 가장 핵심적인 서비스들에 5년 넘게 사용되며 안정성을 입증했습니다. 가용성이 극도로 중요한 Rust 기반 네트워크 서비스를 운영한다면, `ecdysis`는 복잡한 소켓 공유 로직을 직접 구현할 필요 없이 제로 다운타임 업데이트를 구현할 수 있는 가장 실무적인 해결책이 될 것입니다.

대규모 환경의 Rust (새 탭에서 열림)

WhatsApp은 최근 30억 명 이상의 사용자들을 멀웨어 위협으로부터 보호하기 위해 미디어 처리 라이브러리를 Rust 언어로 재구축하여 성공적으로 배포했습니다. 이는 글로벌 규모의 서비스에서 Rust가 프로덕션 환경에 적합함을 증명한 사례로, 특히 메모리 안전성이 취약한 C/C++ 기반의 미디어 파싱 라이브러리에서 발생할 수 있는 보안 취약점을 근본적으로 해결하는 데 중점을 두었습니다. 결과적으로 WhatsApp은 성능과 메모리 효율성을 동시에 개선하면서도 사용자 보안을 한층 더 강화하는 성과를 거두었습니다. **미디어 보안의 취약점과 대응의 역사** - 이미지나 영상처럼 무해해 보이는 파일도 운영체제의 취약점을 공격하는 악성 코드를 포함할 수 있으며, 2015년 안드로이드의 'Stagefright' 취약점이 대표적인 사례입니다. - 당시 WhatsApp은 OS 라이브러리의 패치를 기다리는 대신, 자체 개발한 C++ 기반의 미디어 일관성 검사 라이브러리인 'wamedia'를 통해 표준을 준수하지 않는 파일을 사전에 차단하는 방식을 택했습니다. - 하지만 미디어 체크 로직 자체가 신뢰할 수 없는 입력을 자동으로 처리하기 때문에, 이 라이브러리 자체의 메모리 안전성을 확보하는 것이 보안의 핵심 과제로 떠올랐습니다. **Rust를 통한 대규모 현대화 및 성능 개선** - WhatsApp은 점진적인 수정 대신 기존 C++ 버전과 병행하여 Rust 버전의 라이브러리를 새롭게 개발했습니다. - 두 언어 간의 호환성을 보장하기 위해 '디퍼런셜 퍼징(Differential Fuzzing)'과 광범위한 통합 테스트를 거쳐 안전성을 검증했습니다. - 기존 160,000줄의 C++ 코드를 90,000줄의 Rust 코드로 대체했으며, 결과적으로 이전보다 더 우수한 성능과 낮은 런타임 메모리 사용량을 기록했습니다. - 안드로이드, iOS, 웹, 웨어러블 등 다양한 플랫폼 지원을 위한 빌드 시스템 구축과 바이너리 크기 최적화라는 기술적 난관을 극복하고 글로벌 배포를 완료했습니다. **다층 방어 체계 'Kaleidoscope'의 구축** - Rust로 작성된 이 라이브러리들은 'Kaleidoscope'라 불리는 종합 보안 체크 시스템의 핵심 구성 요소입니다. - 단순히 파일 구조의 결함을 찾는 것을 넘어, PDF 내의 스크립트 요소나 임베디드 파일, 확장자를 위조한 MIME 타입 변조 등을 감지합니다. - 실행 파일이나 앱 설치 파일과 같은 위험한 파일 형식을 식별하여 사용자 인터페이스(UX) 차원에서 특별 관리함으로써 비공식 클라이언트나 악성 첨부파일로부터 사용자를 보호합니다. **메모리 안전 언어 중심의 보안 로드맵** - WhatsApp의 분석에 따르면 심각도가 높은 취약점의 대부분은 C/C++의 메모리 관리 문제에서 발생하며, 이를 해결하기 위해 새로운 코드 작성 시 메모리 안전 언어(Memory Safe Language)를 기본으로 선택하고 있습니다. - 불필요한 공격 표면을 최소화하고, 기존 C/C++ 코드에 대해서는 강화된 메모리 할당자와 보안 버퍼 API를 적용하는 등 보안 보증 투자를 병행하고 있습니다. - 이번 Rust 도입의 성공을 바탕으로 향후 더 많은 영역에 Rust 채택을 가속화하여 내부 방어 체계를 지속적으로 강화할 계획입니다. **결론 및 제언** WhatsApp의 사례는 보안이 중요한 클라이언트 사이드 애플리케이션에서 Rust가 단순한 대안을 넘어 최고의 선택지가 될 수 있음을 보여줍니다. 특히 외부에서 유입되는 미가공 데이터를 파싱해야 하는 시스템이라면, 메모리 안전성이 보장되는 Rust로의 전환을 통해 보안 사고의 근본 원인을 제거하고 운영 효율성을 높이는 전략을 적극 검토할 필요가 있습니다.

CNAME이 먼저인가 A 레 (새 탭에서 열림)

Cloudflare의 DNS 서비스인 1.1.1.1은 메모리 사용량을 최적화하기 위해 DNS 응답 내 레코드 순서를 변경했다가 전 세계적인 접속 장애를 일으켰습니다. 대다수 현대 소프트웨어는 DNS 레코드 순서를 무시하지만, glibc와 같은 특정 구현체는 CNAME 레코드가 A 레코드보다 먼저 등장할 것을 전제로 작동하기 때문입니다. 결국 Cloudflare는 이전의 순서로 로직을 롤백하여 문제를 해결했습니다. ### CNAME 체인과 부분 캐싱 메커니즘 * **DNS 별칭 추적:** `www.example.com`을 조회할 때 리졸버는 최종 IP 주소에 도달할 때까지 여러 개의 CNAME(별칭)을 따라가며, 이 과정에서 발생하는 모든 중간 레코드를 캐싱합니다. * **부분 만료 처리:** 체인 내 레코드들은 각기 다른 TTL(유효 기간)을 가집니다. 일부 CNAME은 유효하지만 최종 A 레코드가 만료된 경우, 리졸버는 전체 체인을 다시 조회하는 대신 만료된 부분만 갱신하여 기존 캐시와 병합합니다. * **병합 과정의 중요성:** 갱신된 레코드와 기존 캐시 레코드를 하나의 응답으로 합칠 때, 이들의 배열 순서가 클라이언트의 해석 방식에 영향을 미칩니다. ### 성능 최적화를 위한 로직 변경 * **기존 방식 (CNAME 우선):** 새로운 리스트를 생성하여 캐시된 CNAME들을 먼저 넣고, 그 뒤에 새로 조회된 A 레코드를 추가했습니다. 이는 메모리 할당과 복사 비용이 추가로 발생합니다. * **변경 방식 (A 레코드 우선):** 메모리 사용량을 줄이기 위해 기존의 응답 리스트 끝에 CNAME 레코드를 단순히 덧붙이는(append) 방식으로 변경했습니다. * **결과:** 이 사소한 변경으로 인해 DNS 응답 데이터에서 CNAME이 최종 결과값인 A 레코드보다 뒤에 위치하게 되었습니다. ### glibc 등 DNS 클라이언트의 처리 방식 문제 * **순차적 탐색:** 리눅스에서 널리 사용되는 `glibc`의 `getaddrinfo`와 같은 구현체는 DNS 응답을 순차적으로 읽으며 '찾아야 할 이름'을 업데이트합니다. * **인식 실패:** 클라이언트가 CNAME을 먼저 발견하면 "다음 타겟 이름"을 갱신하고 이후에 나오는 A 레코드를 수락합니다. 하지만 A 레코드가 먼저 나오면 아직 CNAME 정보를 모르기 때문에 해당 레코드를 무관한 데이터로 간주하고 무시합니다. * **결과적 오류:** 모든 데이터를 읽었음에도 불구하고 클라이언트는 매칭되는 IP를 찾지 못해 최종적으로 응답이 비어 있다는 결론을 내리게 됩니다. ### 시사점 및 결론 40년 된 DNS 프로토콜의 모호성으로 인해 레코드 순서에 대한 엄격한 정의가 부족할 수 있지만, 실제 환경에서는 전통적인 순서(CNAME -> Answer)를 유지하는 것이 하위 호환성을 위해 필수적입니다. 시스템의 성능 최적화가 기존 생태계의 암묵적인 동작 원리를 깨뜨리지 않는지, 특히 표준 라이브러리 수준의 하위 호환성을 철저히 검증해야 함을 보여주는 사례입니다.

Evolving our real-time timeseries storage again: Built in Rust for performance at scale (새 탭에서 열림)

데이터독(Datadog)은 급증하는 데이터 볼륨과 고카디널리티(high-cardinality) 워크로드를 처리하기 위해 Rust 기반의 6세대 실시간 시계열 데이터베이스 엔진을 새롭게 설계했습니다. 기존 시스템의 한계를 극복하기 위해 인제스션(Ingestion), 저장, 쿼리 실행 구조를 근본적으로 재구성함으로써 수집 성능은 60배, 쿼리 속도는 최대 5배까지 향상시키는 성과를 거두었습니다. 이 글은 지난 15년간 데이터독이 카산드라에서 시작해 Rust 기반의 전용 엔진에 이르기까지 거쳐온 기술적 진화 과정과 그 과정에서 얻은 교훈을 다룹니다. ### 데이터독 시계열 저장소의 아키텍처 데이터독의 메트릭 플랫폼은 데이터의 효율적인 처리를 위해 실시간 저장소와 인덱스 데이터베이스를 분리하여 운영합니다. * **RTDB (Real-time DB):** `<timeseries_id, timestamp, value>` 형태의 원시 메트릭 데이터를 저장하고 집계하며, 최신 데이터를 실시간으로 서빙합니다. * **인덱스 데이터베이스:** 메트릭 식별자와 태그 정보를 `<timeseries_id, tags>` 형태로 관리합니다. * **데이터 흐름:** 쿼리가 발생하면 상위 서비스가 RTDB와 인덱스 노드에 각각 접속하여 결과를 가져오고, RTDB 노드 내부는 인테이크(Intake), 스토리지 엔진, 스냅샷 모듈, gRPC 쿼리 실행 계층 등으로 구성되어 유기적으로 동작합니다. ### 1세대부터 3세대: 확장성과 운영 효율의 탐색 초기 데이터독은 기성 솔루션을 활용하며 실시간 쿼리 성능과 운영 편의성을 확보하는 데 집중했습니다. * **Gen 1 (Cassandra):** 뛰어난 쓰기 확장성을 제공했으나, 알람 및 분석에 필요한 복잡한 실시간 쿼리를 지원하기 어렵고 대규모 데이터셋 반환 시 효율이 떨어지는 한계가 있었습니다. * **Gen 2 (Redis):** 빠른 읽기 속도와 운영 가시성을 제공했지만, 싱글 스레드 특성상 라이브 트래픽 처리 중 스냅샷 작업이 어려웠고 데이터 직렬화/역직렬화에 따른 CPU 및 메모리 비용이 증가했습니다. * **Gen 3 (MDBM):** `mmap`을 통해 OS 페이지 캐시를 활용하는 메모리 맵 방식의 키-값 저장소를 도입했으나, 대규모 워크로드에서 성능과 정확성 이슈가 발생하며 명시적인 I/O 관리의 필요성을 체감했습니다. ### 4세대와 5세대: 커스텀 엔진과 기능 확장 성능 한계를 돌파하기 위해 범용 DB를 벗어나 전용 스토리지 엔진을 직접 구현하기 시작했습니다. * **Gen 4 (Go 기반 B+ Tree):** Go 언어로 구현된 커스텀 B+ 트리 엔진을 도입하여 '코어당 스레드(thread-per-core)' 모델의 기초를 닦았으며, 처리량과 지연 시간 면에서 큰 진전을 이루었습니다. * **Gen 5 (RocksDB 통합):** 분포 메트릭(distribution metrics)과 DDSketch 타입을 지원하기 위해 RocksDB를 병행 도입했습니다. 하지만 기존 Go 엔진과 RocksDB가 공존하는 구조는 관리가 복잡하고 효율성이 분산되는 결과를 낳았습니다. ### 6세대: Rust 기반의 통합 엔진으로의 전환 파편화된 엔진을 통합하고 성능을 극대화하기 위해 Rust를 선택하여 차세대 시스템을 구축했습니다. * **통합 및 최적화:** 스칼라 값과 스케치 데이터를 모두 처리할 수 있는 단일 엔진을 Rust로 구축하여 언어 차원의 안정성과 고성능 I/O 제어권을 확보했습니다. * **성능 성과:** 이 구조적 변화를 통해 데이터 수집 성능을 60배 높였으며, 피크 시간대 쿼리 속도를 5배 향상시켜 전례 없는 규모의 트래픽을 효율적으로 수용하게 되었습니다. **결론 및 추천** 시스템 규모가 커짐에 따라 범용 데이터베이스나 `mmap`과 같은 추상화 계층은 오히려 성능 병목이 될 수 있습니다. 데이터독의 사례처럼 워크로드의 특성에 맞춰 I/O와 메모리 레이아웃을 직접 제어할 수 있는 전용 엔진을 구축하는 것이 기술적 부채를 해결하고 폭발적인 성장을 뒷받침하는 핵심 전략이 될 수 있습니다. 특히 Rust와 같은 시스템 프로그래밍 언어는 고성능 실시간 시스템을 재설계할 때 강력한 도구가 됩니다.

서버 의존성 없이 실시간 클라이언트 사이드 노이즈 억제 라이브러리를 구축한 방법 (새 탭에서 열림)

Datadog의 CoScreen 팀은 원격 협업 중 발생하는 배경 소음을 실시간으로 제거하기 위해, 높은 성능과 이식성을 갖춘 오픈소스 라이브러리인 `dtln-rs`를 개발했습니다. 기존의 노이즈 억제 도구들은 WebRTC와의 통합이 어렵거나 고성능 서버 자원을 요구한다는 한계가 있었으나, 이 라이브러리는 클라이언트 측에서 효율적으로 동작하도록 설계되었습니다. 결과적으로 M1 맥북 프로 기준 1초의 오디오를 단 33ms 만에 처리하며, 서버 의존성 없이 다양한 플랫폼에서 고품질의 실시간 소음 제거를 가능하게 합니다. **dtln-rs: 경량화된 실시간 노이즈 억제 라이브러리** * DTLN(Dual-Signal Transformation LSTM Network) 모델을 기반으로 구축된 Rust 언어 기반의 프로젝트입니다. * WebAssembly(WASM), Native Rust, Node.js 네이티브 모듈 등 다양한 타겟으로 빌드할 수 있어 웹 브라우저와 네이티브 앱 모두에 쉽게 통합 가능합니다. * 실제 테스트에서 이웃의 잔디 깎는 기계 소음을 완전히 제거할 정도로 뛰어난 성능을 보여주었으며, 이를 통해 사용자에게 실제적인 가치를 전달합니다. **DTLN 모델의 작동 원리와 효율성** * STFT(단시간 푸리에 변환)를 사용하여 소리를 작은 단위로 분해하고, 주파수별 볼륨(크기 스펙트럼)과 위상(Phase) 정보를 분석합니다. * LSTM(장단기 메모리) 신경망이 포함된 모델을 통해 분석된 데이터 중 어떤 부분이 음성이고 어떤 부분이 소음인지 실시간으로 판단합니다. * 위상 정보와 크기 성분을 모두 활용하는 딥러닝 방식 덕분에 에어컨 소음, 카페 소음, 종이 부스럭거리는 소리 등 다양한 환경에 동적으로 적응하며 즉각적인 감쇠가 가능합니다. **기존 기술의 한계와 개발 배경** * 기존의 고성능 AI 노이즈 제거 모델들은 대부분 강력한 서버 하드웨어를 필요로 하며, 이는 추가적인 지연 시간(Latency)과 막대한 서버 비용을 발생시킵니다. * WebRTC는 널리 쓰이는 오픈소스 기술임에도 불구하고 내장된 노이즈 제거 기능은 구세대 솔루션에 머물러 있어 현대적인 협업 도구들의 품질 요구 수준을 충족하지 못했습니다. * Google 등 대기업이 사용하는 최첨단 모델은 비공개 소스이거나 전용 서버 인프라에 종속되어 있어, 소규모 팀이나 일반 개발자들이 자신들의 앱에 고품질 기능을 구현하기에는 제약이 컸습니다. 실시간 오디오 및 비디오 애플리케이션을 개발하면서 서버 비용 부담 없이 고성능 노이즈 캔슬링 기능을 추가하고 싶다면 `dtln-rs`를 검토해 보시기 바랍니다. 클라이언트 측 리소스를 효율적으로 활용하면서도 WebRTC와 매끄럽게 결합되는 이 라이브러리는 사용자 경험을 한 단계 끌어올리는 실용적인 해결책이 될 것입니다.

Squeezing every millisecond: How we rebuilt the Datadog Lambda Extension in Rust (새 탭에서 열림)

Datadog은 기존 Go 기반의 AWS Lambda 확장이 가진 높은 오버헤드를 해결하기 위해, 이를 Rust 언어로 완전히 재작성한 'Project Bottlecap'을 진행했습니다. 이를 통해 콜드 스타트 시간을 82% 단축하고 메모리 사용량을 40% 절감했으며, 바이너리 크기를 55MB에서 7MB로 줄이는 획기적인 성능 개선을 달성했습니다. 결과적으로 리소스가 제한된 서버리스 환경에서도 사용자 애플리케이션에 영향을 주지 않고 고정밀 텔레메트리 데이터를 수집할 수 있게 되었습니다. ### 기존 범용 에이전트 기반 설계의 한계 - 초기 Datadog Lambda 확장은 다중 호스트나 클러스터 환경에 최적화된 기존 Datadog 에이전트 코드를 기반으로 구축되었습니다. - 범용 에이전트는 대규모 처리량과 캐싱, 버퍼링에 초점이 맞춰져 있어 리소스가 극도로 제한된 람다의 단기 실행 환경에는 부적합했습니다. - 종속성 제거, 바이너리 압축(UPX), 지연 로딩 등 모든 최적화 수단을 동원했음에도 불구하고 콜드 스타트 지연 시간이 450~500ms 이하로 내려가지 않는 성능 한계에 직면했습니다. - 결국 범용 도구와 서버리스 전용 도구의 스케일 차이를 인정하고, 밑바닥부터 다시 작성하는 결정을 내렸습니다. ### Lambda 환경에서 Rust 언어의 전략적 이점 - **안정성 및 메모리 안전성:** 람다 확장이 충돌하면 함수 전체가 종료되고 샌드박스가 초기화되어 다시 콜드 스타트가 발생하는데, Rust는 컴파일 타임에 메모리 안전성을 보장하여 이러한 위험을 최소화합니다. - **바이너리 경량화:** 가비지 컬렉터와 대규모 런타임이 포함된 Go와 달리, Rust는 킬로바이트 또는 낮은 메가바이트 단위의 매우 작은 바이너리를 생성하여 초기 로딩 시간을 줄입니다. - **제한된 환경의 이점:** 람다는 아마존 리눅스와 x86/Arm 아키텍처라는 고정된 환경만 고려하면 되므로, 다양한 환경을 지원해야 하는 시스템 프로그래밍에서 Rust가 가질 수 있는 복잡성 문제가 크게 완화되었습니다. ### Project Bottlecap의 핵심 설계 원칙 - **철저한 성능 오버헤드 통제:** 모든 풀 리퀘스트(PR)마다 벤치마크를 수행하여 성능 저하를 감시했으며, 성능 향상을 위해 공식 AWS SDK 사용을 포기하고 직접 AWS API 호출과 서명 로직을 작성하는 트레이드오프를 감수했습니다. - **핸들러 영향 최소화:** 람다 확장 API를 활용하여 함수 핸들러가 결과를 반환한 후에 텔레메트리를 처리함으로써, 사용자 API 응답 속도에 미치는 영향을 제거했습니다. - **다양한 플러시(Flush) 전략:** 리소스 사용량이 적은 API 함수부터 대규모 배치 작업까지 대응할 수 있도록 데이터 전송 시점을 유연하게 설정할 수 있는 구조를 갖추었습니다. 범용 소프트웨어를 특정 환경에 맞춰 최적화하는 것에는 한계가 있습니다. 특히 실행 시간과 리소스 사용량이 곧 비용과 직결되는 서버리스 환경에서는, 해당 환경의 제약 조건을 반영한 전용 도구를 구축하는 것이 초기 개발 비용이 높더라도 장기적으로 성능과 안정성 측면에서 압도적인 이점을 제공합니다.

How we migrated our static analyzer from Java to Rust (새 탭에서 열림)

Datadog은 정적 분석 도구의 성능 병목 현상을 해결하고 제한된 CI 환경에서의 효율성을 극대화하기 위해 기존 Java 기반의 엔진을 Rust로 완전히 재작성했습니다. 이 과정에서 ANTLR 대신 Tree-sitter를 도입하고 JavaScript 규칙 실행 엔진을 GraalVM에서 Deno(V8)로 교체함으로써, 분석 속도는 3배 향상시키고 메모리 사용량은 10배 절감하는 성과를 거두었습니다. 결과적으로 이번 전환은 고성능 정적 분석을 위해 언어와 런타임 수준의 근본적인 변화가 필수적이었음을 보여줍니다. **Java 기반 정적 분석기의 한계와 환경적 제약** * **CI 자원 최적화 문제:** 고객의 CI 환경(예: GitHub Actions)에서 분석기를 실행할 때, 2코어 및 7GB RAM과 같은 제한된 자원 내에서 Java 분석기는 수천 개의 파일을 스캔하는 데 5분 이상 소요되어 목표치(3분 이내)를 충족하지 못했습니다. * **환경 충돌 및 오버헤드:** Java 기반 분석기는 최신 JVM(17+)을 요구하는데, 이는 고객의 기존 CI 환경에 설치된 Java 버전과 충돌을 일으키거나 불필요한 설정 부담을 주었습니다. * **파싱 성능 저하:** 기존에 사용하던 ANTLR은 대규모 저장소에서 파싱 속도가 느렸고, 특정 언어에 대한 지원이 부분적이라는 기술적 한계가 있었습니다. **Tree-sitter 도입과 Rust로의 전환 결정** * **파서 교체:** 성능 향상을 위해 C로 구현되어 속도가 빠르고 오픈소스 커뮤니티가 활발한 Tree-sitter를 채택했습니다. * **Java 라이브러리의 한계:** Tree-sitter용 Java 라이브러리는 분석기 최적화에 필수적인 패턴 매칭 기능을 지원하지 않는 등 기능이 제한적이었습니다. * **언어 선택의 기로:** Tree-sitter가 가장 견고하게 지원하는 언어가 Rust라는 점에 착안하여, 예측 가능한 성능을 위해 Rust로의 전체 재작성이라는 도전적인 경로를 선택했습니다. **Rust 기반의 새로운 아키텍처 구성** * **AST 구축(Tree-sitter):** Rust는 Tree-sitter 생태계의 "일등 시민(First-class citizen)"으로, 직접적인 라이브러리 연동을 통해 별도의 Java 바인딩 유지보수 없이도 강력한 파싱 기능을 확보했습니다. * **자바스크립트 규칙 실행(Deno):** 정적 분석 규칙은 자바스크립트로 작성되는데, 기존 Java의 GraalVM 대신 Deno(deno-core)를 런타임으로 도입했습니다. * **보안 및 효율성:** Deno의 V8 엔진을 활용하되, 분석 규칙이 디스크나 네트워크에 접근하지 못하도록 `deno-core` 크레이트만 통합하여 보안이 강화된 샌드박스 환경을 구축했습니다. **마이그레이션 결과 및 기술적 권고** 성공적인 Rust 전환을 통해 동일한 프로그램에 대해 Java 버전과 일치하는 분석 결과를 보장하면서도 성능은 3배, 메모리 효율은 10배 개선되었습니다. 특히 리소스가 제한된 CI/CD 파이프라인에서 정적 분석 도구를 운영해야 한다면, JVM과 같은 무거운 런타임보다는 Rust와 같이 저수준 제어가 가능하고 메모리 오버헤드가 적은 언어를 선택하는 것이 장기적으로 유리합니다.

.NET Continuous Profiler: CPU and wall time profiling (새 탭에서 열림)

Datadog의 .NET 컨티뉴어스 프로파일러는 CPU 사용량과 Wall Time(실행 시간)을 효과적으로 수집하기 위해 저수준 스레드 샘플링 방식을 채택하고 있습니다. 운영 환경의 부하를 최소화하면서도 정확한 데이터를 확보하기 위해 관리되는 스레드(Managed Threads)를 정밀하게 추적하며, 가비지 컬렉션(GC)과 같은 네이티브 스레드의 영향까지 함께 분석합니다. 이를 통해 개발자는 연산 집약적인 코드뿐만 아니라 I/O 대기 등으로 인한 지연 원인까지 심층적으로 파악할 수 있습니다. ### CPU와 Wall Time 프로파일링의 개념적 차이 * **CPU 프로파일링**: 스레드가 CPU 코어에서 실제로 실행되는 동안 소모한 사이클을 측정하여 연산량이 많은 코드 블록을 찾는 데 집중합니다. * **Wall Time 프로파일링**: I/O 대기나 락(Lock) 경합 등 스레드가 중단된 시간까지 포함하여 메서드 실행에 걸린 전체 시간을 측정하며, 요청 지연의 근본 원인을 파악하는 데 유용합니다. * **샘플링 방식 채택**: ETW(Windows)나 perf(Linux) 같은 도구는 높은 권한과 시스템 부하 문제로 운영 환경에 부적합하므로, 특정 주기로 스레드 스택을 관찰하는 샘플링 방식을 사용하여 성능 영향을 최소화합니다. ### 효율적인 스레드 모니터링 구조 * **관리되는 스레드 추적**: `ICorProfilerCallback`의 메서드들을 활용해 .NET 런타임이 관리하는 스레드의 생성 및 파괴를 실시간으로 모니터링하고 `ManagedThreadList`에 보관합니다. * **네이티브 스레드 오탐 방지**: 초기 구현에서는 C#을 사용했으나, 네이티브 스레드가 관리되는 메서드를 호출할 때 발생하는 예외적인 상황을 방지하기 위해 전체 구조를 C++로 작성하여 프로파일러 자체 스레드가 샘플링되는 문제를 해결했습니다. * **공용 익스포터 활용**: 수집된 샘플 데이터는 Rust로 작성된 고성능 익스포터를 통해 Datadog 백엔드로 전송되며, 이 모듈은 PHP, Ruby 등 다른 언어 프로파일러와 공유되어 안정성을 확보했습니다. ### OS 수준의 CPU 프로파일링 최적화 * **상태 확인 메커니즘**: 10ms마다 실행 가능한 스레드를 검사하며, Windows는 `NtQueryInformationThread`를, Linux는 `/proc/self/task/<tid>/stat` 파일을 파싱하여 CPU 소비량을 확인합니다. * **저수준 C 구현을 통한 성능 개선**: Linux 환경에서 `std::ifstream` 등 고수준 C++ 클래스를 사용할 때 발생하는 메모리 할당 오버헤드를 줄이기 위해, 할당이 없는 저수준 C API로 교체하여 전체 메모리 할당량의 8%와 CPU 사용량의 2%를 절감했습니다. * **GC 스레드 가시화**: .NET 5 이상의 환경에서는 프로파일링 API가 감지하지 못하는 서버 GC 및 배경 GC 스레드의 CPU 소비량을 별도로 계산하여 플레임 그래프에 표시함으로써 성능 간섭 현상을 명확히 보여줍니다. ### 분산 추적과 연동된 Wall Time 분석 * **Code Hotspots 기능**: 분산 트레이서와 연동하여 특정 요청(Span)을 처리 중인 스레드를 우선적으로 샘플링하며, 이를 통해 느린 요청의 원인이 되는 코드 경로를 정확히 짚어냅니다. * **P/Invoke 비용 최소화**: 트레이서가 프로파일러를 호출할 때 발생하는 오버헤드를 줄이기 위해, 스팬 ID가 기록되는 메모리 위치를 직접 공유하여 추가적인 API 호출 없이 데이터를 실시간으로 읽어옵니다. * **동적 샘플링**: 실행 중인 스레드가 많아질수록 샘플링 간격을 조절하여 데이터의 정확도와 시스템 부하 사이의 균형을 유지합니다. 이 프로파일러는 고성능 환경에서 안정적으로 동작하기 위해 C++와 Rust를 기반으로 저수준 OS API를 직접 제어하도록 설계되었습니다. 특히 Linux 환경에서의 파일 파싱 최적화나 트레이서와의 메모리 공유 방식은 대규모 트래픽을 처리하는 서비스에서 프로파일러 자체의 오버헤드를 극단적으로 줄여야 하는 개발자들에게 유용한 참고 사례가 됩니다.

피그마가 게임 세계에서 (새 탭에서 열림)

피그마는 단순한 웹 애플리케이션을 넘어 고성능 게임 엔진과 유사한 기술적 아키텍처를 기반으로 구축된 창의적 협업 도구입니다. 이 글은 피그마가 실시간 멀티플레이어 시스템, 물리 기반 애니메이션, 그리고 C++와 WebAssembly, Rust와 같은 고성능 스택을 통해 어떻게 디지털 세계를 구축하는지 설명합니다. 결과적으로 피그마는 게임 개발의 복잡한 시스템 상호작용 원리를 차용하여 사용자들에게 몰입감 있고 매끄러운 디자인 경험을 제공하고 있습니다. ## 디지털 세계를 구축하는 엔진으로서의 피그마 * 피그마의 핵심은 웹 기반의 2D 그래픽 및 렌더링 시스템으로, 이는 마인크래프트와 같은 게임 엔진의 근간과 동일한 구조를 가집니다. * 사용자가 생성하는 모든 텍스트, 도형, 선을 브라우저에서 실시간으로 구현하며, 방대한 캔버스에서의 팬(pan)과 줌(zoom) 조작 시에도 정확한 위치에 객체를 렌더링합니다. * 실시간 동시 편집 기능을 게임의 개념에서 착안한 '멀티플레이어(multiplayer)' 엔진이라고 명명하여 협업의 핵심 시스템으로 발전시켰습니다. * 브라우저 및 모바일 앱의 메모리와 성능 제약을 극복하기 위해 일반적인 웹 스택 대신 C++로 캔버스를 구축한 후 WebAssembly로 컴파일하여 로딩 속도를 3배 개선했으며, 서버 측 성능 향상을 위해 Rust 언어를 도입했습니다. ## 시스템 기반의 창의적 협업과 상호작용 * 게임 스튜디오에서 엔지니어와 아티스트가 협업하듯, 피그마 엔지니어들은 시스템의 한계를 밀어붙이기 위해 디자이너, PM, 데이터 과학자들과 긴밀하게 소통합니다. * '젤다의 전설: 브레스 오브 더 와일드'의 불(fire) 시스템이 빛, 온기, 공격 수단 등 다양한 방식으로 상호작용하는 것처럼, 피그마의 오토세이브, 멀티플레이어, 렌더링 시스템도 서로 유기적으로 연결되어 작동합니다. * 단순한 도구 기능을 넘어 스프링 물리 법칙을 적용한 애니메이션 시스템, 커서 채팅, 하이파이브 기능 등을 통해 사용자가 도구 내에서 살아있는 피드백을 느낄 수 있도록 설계했습니다. * 베리언트(Variants) 기능과 플러그인/위젯 시스템을 통해 디자인 컴포넌트와 코드를 긴밀하게 연결하고, 사용자가 직접 생태계를 확장할 수 있는 개방형 플랫폼을 지향합니다. 웹 환경에서 복잡하고 성능 집약적인 도구를 개발해야 한다면, 전통적인 웹 프레임워크의 틀을 벗어나 게임 엔진의 설계 방식과 고성능 언어(WASM, Rust) 도입을 검토해야 합니다. 기술적 한계를 극복하는 열쇠는 도구를 하나의 살아있는 '시스템'들의 집합으로 바라보고, 각 요소 간의 상호작용이 사용자 경험에 미치는 영향을 정교하게 설계하는 데 있습니다.

Introducing Glommio, a thread-per-core crate for Rust and Linux (새 탭에서 열림)

현대적인 클라우드 비용 절감과 성능 최적화를 위해서는 단순히 코드의 병목점을 찾는 것을 넘어, 하드웨어 성능을 극대화할 수 있는 '코어당 스레드(thread-per-core)' 아키텍처로의 전환이 필요합니다. 이 아키텍처는 애플리케이션의 꼬리 지연 시간(tail latency)을 최대 71%까지 개선할 수 있지만, 구현이 복잡하고 개발 생산성을 떨어뜨릴 수 있다는 단점이 있습니다. Datadog은 이러한 문제를 해결하기 위해 Rust 개발자들이 코어당 스레드 모델을 쉽게 구현할 수 있도록 돕는 오픈소스 라이브러리 'Glommio'를 제안합니다. **기존 멀티스레드 모델의 한계와 비용** * 전통적인 멀티스레딩 방식은 여러 스레드가 동일한 데이터에 접근할 때 데이터 정합성을 보장하기 위해 락(Lock)을 사용하며, 이는 실행을 멈추고 대기하는 시간을 발생시켜 비용을 증가시킨다. * 스레드 간 컨텍스트 스위칭(Context Switching)은 약 5마이크로초의 비용이 드는데, 이는 io_uring과 같은 최신 커널 인프라의 I/O 처리 시간(4마이크로초 미만)보다 길다. 즉, 스레드 전환 비용이 실제 I/O 작업보다 비싸지는 역전 현상이 발생한다. * 기존의 비동기 프로그래밍 방식 또한 파일 I/O 등을 처리할 때 내부적으로 스레드 풀을 사용해야 하는 경우가 많아 완전한 효율성을 달성하기 어렵다. **코어당 스레드(Thread-per-core)의 동작 원리** * 각 CPU 코어에 단 하나의 스레드만 할당하고 이를 특정 코어에 고정(Pinning)함으로써, OS 스케줄러에 의한 스레드 이동과 컨텍스트 스위칭을 원천적으로 차단한다. * 하드웨어 인터럽트나 시스템 에이전트와 같은 외부 작업을 위해 특정 코어를 전용으로 분리하고, 나머지 코어를 애플리케이션 스레드에 할당하여 간섭을 최소화한다. * 모든 작업은 협력적 스케줄링(Cooperative Scheduling) 방식으로 동작하며, 실행 중인 작업이 명시적으로 제어권을 양보하거나 완료될 때까지 CPU를 점유하여 선점형 스케줄링의 오버헤드를 없앤다. **데이터 샤딩을 통한 락 프리(Lock-free) 구현** * 각 스레드가 전체 데이터의 특정 부분집합(Shard)만을 담당하도록 설계하여, 서로 다른 스레드가 동일한 데이터에 접근할 가능성을 차단한다. * 예를 들어 특정 카프카 파티션이나 데이터베이스 키 범위별로 담당 스레드를 지정함으로써, 스레드 간의 데이터 공유를 최소화한다. * 데이터 업데이트 작업이 해당 스레드 내에서 자연스럽게 직렬화(Serialized)되므로, 복잡하고 비용이 큰 락(Lock) 메커니즘 없이도 안전하게 데이터를 조작할 수 있다. **Glommio를 통한 Rust 애플리케이션 최적화** * Glommio는 C++의 Seastar 프레임워크와 유사한 철학을 Rust 환경에 가져와, 개발자가 코어당 스레드 아키텍처의 복잡한 세부 사항을 직접 다루지 않고도 고성능 애플리케이션을 작성할 수 있게 한다. * 성능 민감도가 높은 대규모 데이터 스토어(Datastores)나 고처리량 메트릭 수집 시스템을 운영하는 팀에게 Glommio는 하드웨어 효율성과 개발 편의성을 동시에 잡을 수 있는 실질적인 대안이 된다.