memory-management

3 개의 포스트

Pinterest의 Apache Spark에서 (새 탭에서 열림)

Pinterest는 대규모 Spark 환경에서 빈번하게 발생하는 OOM(Out-of-Memory) 오류를 해결하기 위해 'Auto Memory Retries' 기능을 도입했습니다. 이 시스템은 태스크 수준에서 리소스 요구량을 동적으로 판단하고, 실패 시 더 큰 메모리 프로필을 가진 실행기(Executor)에서 태스크를 재시도하도록 자동화합니다. 이를 통해 수동 튜닝의 번거로움을 줄이고 자원 효율성을 높여 전체적인 작업 실패율과 운영 비용을 획기적으로 낮추는 성과를 거두었습니다. ### 기존 Spark 리소스 관리의 한계와 문제점 * Pinterest의 Spark 클러스터는 하드웨어 대비 높은 메모리 요구량으로 인해 OOM 오류가 잦았으며, 전체 작업 실패 원인의 약 4.6%가 메모리 부족에서 기인했습니다. * 사용자가 모든 스테이지와 태스크의 메모리 요구량을 정확히 예측하여 수동으로 설정하는 것은 매우 어렵고 시간이 많이 소요되는 작업입니다. * 데이터 스큐(Skew) 현상으로 인해 같은 스테이지 내에서도 특정 태스크만 과도한 메모리를 사용하는 경우가 많아, 모든 태스크를 최대치에 맞춰 설정하면 심각한 자원 낭비가 발생합니다. * 제품 팀의 우선순위 문제로 인해 비용 절감을 위한 수동 최적화가 지속적으로 이루어지기 어려운 구조적 한계가 있었습니다. ### Auto Memory Retries의 단계별 대응 전략 * **CPU 할당량 증설을 통한 메모리 확보 (1단계):** 실행기에 1개 이상의 코어가 있는 경우, OOM 발생 시 첫 번째 재시도에서 태스크당 CPU 할당량(`spark.task.cpus`)을 두 배로 늘립니다. 이를 통해 실행기 내 동시 실행 태스크 수를 줄여 개별 태스크가 사용할 수 있는 공유 메모리 공간을 즉각적으로 확보합니다. * **물리적으로 큰 실행기 투입 (2단계):** CPU 조절만으로 해결되지 않거나 단일 태스크가 이미 실행기 전체 메모리를 사용 중인 경우, 물리적으로 더 큰 메모리를 가진 새로운 실행기를 동적으로 런칭합니다. * **하이브리드 확장 프로필 적용:** 기본 설정의 2배, 3배, 4배 크기의 리소스 프로필을 미리 등록하고 단계별로 순차 적용합니다. Apache Gluten을 사용하는 워크로드의 경우 Off-heap 메모리도 함께 증설하여 가속화된 연산을 지원합니다. ### 시스템 구현 및 Spark 엔진 확장 * **태스크 수준의 리소스 프로필:** 기존 Spark의 고정된 리소스 할당 방식에서 벗어나, `Task` 객체에 개별 리소스 프로필 ID(`taskRpId`)를 저장할 수 있도록 확장하여 동일한 TaskSet 내에서도 태스크마다 사양을 다르게 가질 수 있게 구현했습니다. * **스케줄링 로직 최적화:** `TaskSetManager`는 OOM 감지 시 즉시 상위 프로필을 할당하며, `TaskSchedulerImpl`은 증설된 CPU 속성을 가진 태스크를 기존 실행기에서 우선 실행할 수 있게 하여 리소스 재사용 속도를 높였습니다. * **동적 리소스 할당:** `ExecutorAllocationManager`가 상위 프로필을 필요로 하는 대기 태스크를 실시간으로 추적하고, 물리적으로 큰 실행기가 필요한 시점에 맞춰 Kubernetes 등에 자원을 요청합니다. * **사용자 경험 개선:** 사용자가 어떤 태스크가 더 많은 자원을 사용했는지 쉽게 파악할 수 있도록 Spark UI의 태스크 목록에 리소스 프로필 ID를 표시하는 기능을 추가했습니다. 효율적인 Spark 운영을 위해서는 모든 작업을 최대 메모리 요구량에 맞추기보다, 상위 90%(P90) 수준의 일반적인 설정으로 실행하고 예외적인 태스크만 'Auto Memory Retries'로 구제하는 탄력적 전략이 권장됩니다. 이는 데이터 스큐가 심한 대규모 파이프라인에서 운영 안정성을 확보함과 동시에 인프라 비용을 최적화할 수 있는 강력한 해법이 될 것입니다.

Rust의 메모리 최적화 (새 탭에서 열림)

Figma는 파일 로드 속도를 혁신적으로 개선하기 위해 전체 파일을 한 번에 불러오는 방식에서 사용자가 현재 보고 있는 페이지만 불러오는 '페이지 단위 온디맨드 로딩' 방식으로 전환했습니다. 이 과정에서 메모리 사용량을 최적화하고 복잡한 동기화 메커니즘을 재설계하여, 특히 대규모 프로젝트에서의 초기 로딩 시간을 최대 50%까지 단축했습니다. 이는 협업 기능을 온전히 유지하면서도 효율적인 자원 관리를 통해 사용자 경험을 극대화한 기술적 성과입니다. **기존 모놀리식 로딩 방식의 한계** * 이전의 Figma는 파일 내 모든 페이지의 데이터를 초기 로딩 시점에 메모리에 전부 적재하는 방식을 사용했습니다. * 파일 규모가 커질수록 로딩 시간이 기하급수적으로 늘어났으며, 브라우저의 메모리 제한으로 인해 대형 파일 실행 시 충돌(Crash)이 발생하는 주요 원인이 되었습니다. * 사용자가 특정 페이지만 작업하더라도 당장 보지 않는 수십 개의 페이지 데이터를 모두 다운로드하고 해석해야 하는 비효율이 존재했습니다. **페이지 단위 온디맨드 로딩 도입** * 파일 저장 구조를 수정하여 각 페이지 데이터를 독립적인 단위로 분리하고, 사용자가 해당 페이지를 클릭할 때만 서버에서 데이터를 가져오도록 변경했습니다. * 초기 로딩 시에는 파일의 전체 구조(페이지 목록, 레이어 트리 등)와 사용자가 마지막으로 머물렀던 페이지만 로드하여 '유효 로딩 시간(Time to Interactive)'을 대폭 줄였습니다. * 페이지 전환 시 지연을 최소화하기 위해 백그라운드에서 데이터를 미리 가져오거나 스트리밍하는 최적화 기법을 적용했습니다. **기술적 난제와 동기화 엔진의 재설계** * Figma의 핵심인 실시간 협업(Multiplayer) 기능을 유지하기 위해, 현재 로드되지 않은 페이지에서 다른 사용자가 수행하는 변경 사항을 추적하고 병합하는 복잡한 로직을 구현했습니다. * C++로 작성된 엔진(Wasm)이 메모리에 없는 데이터에 접근하려 할 때 발생하는 오류를 방지하기 위해 데이터 접근 방식을 프록시화했습니다. * 전체 데이터를 다루던 기존 인덱싱 구조를 부분 로딩 모델에 맞게 재설계하여 데이터 일관성을 보장했습니다. **성능 개선 및 안정성 확보** * 로딩 전략 수정 후 대규모 파일의 초기 로드 속도가 이전 대비 약 30~50% 향상되는 결과를 얻었습니다. * 메모리 사용량을 획기적으로 낮춤으로써 저사양 기기에서의 안정성을 확보하고, 브라우저의 메모리 부족으로 인한 앱 종료 현상을 크게 줄였습니다. * 점진적인 롤아웃과 모니터링을 통해 대규모 데이터 마이그레이션 과정에서 발생할 수 있는 데이터 손실 위험을 성공적으로 관리했습니다. 대규모 데이터를 다루는 웹 애플리케이션에서 초기 로딩 속도와 메모리 관리는 서비스의 생존과 직결됩니다. Figma의 사례처럼 데이터 구조를 세밀하게 파편화하고 '필요한 순간에만 로드'하는 지연 로딩(Lazy Loading) 전략을 시스템 심층부(엔진 레벨)부터 설계하면, 확장성과 사용자 만족도를 동시에 잡을 수 있습니다.

디스코드에서 수백만 (새 탭에서 열림)

디스코드(Discord)는 서비스 초기 광범위한 호환성을 위해 32비트 아키텍처를 선택했으나, 현대적인 컴퓨팅 환경에 발맞춰 64비트 전환의 필요성을 강조하고 있습니다. 32비트는 단일 버전으로 거의 모든 윈도우 환경을 지원할 수 있다는 장점이 있었지만, 엄격한 메모리 제한으로 인해 발생하는 크래시 문제와 라이브러리 지원 중단이라는 한계에 봉착했습니다. 결과적으로 디스코드는 더 높은 안정성과 성능을 제공하기 위해 최신 하드웨어 표준인 64비트 아키텍처로의 이전을 추진하고 있습니다. ### 초기 32비트 선택의 배경과 이점 * **광범위한 호환성**: 2015년 출시 당시 디스코드는 윈도우의 하위 호환성 레이어를 활용해 32비트와 64비트 기기 모두에서 작동하는 단일 앱 버전을 유지하고자 했습니다. * **개발 효율성**: 하나의 실행 파일만 관리하면 되었기에 초기 개발 단계에서 리소스를 집중하고 사용자 접근성을 높이는 데 유리했습니다. * **적은 메모리 사용량**: 이론적으로 32비트 애플리케이션은 64비트에 비해 포인터 크기가 작아 메모리를 덜 사용하는 특성이 있습니다. ### 32비트 아키텍처의 기술적 한계 * **메모리 주소 지정 제한**: 32비트 애플리케이션은 사용할 수 있는 메모리 양에 물리적인 상한선이 존재합니다. 64비트 기기에서 실행하더라도 이 제한을 초과하면 성능 저하나 예기치 않은 크래시가 발생합니다. * **성능 저하 및 오류**: 디스코드가 고도화됨에 따라 메모리 집약적인 작업이 늘어났고, 32비트의 한계치에 도달하여 사용자 경험을 해치는 사례가 빈번해졌습니다. ### 64비트 전환의 필연성 * **라이브러리 생태계의 변화**: 디코드의 기반이 되는 Electron, WebRTC와 같은 주요 라이브러리들은 이미 수년 전부터 64비트를 기본 아키텍처로 채택하고 있습니다. * **유지보수 및 보안 리스크**: 업계 표준이 64비트로 이동함에 따라 32비트 라이브러리에 대한 버그 수정이나 최적화가 줄어들고 있으며, 이는 장기적으로 잠재적인 보안 취약점과 비효율성에 노출될 위험을 높입니다. * **미래 지향적 최적화**: 64비트 환경에서는 더 많은 시스템 자원을 안정적으로 활용할 수 있어, 인게임 오버레이와 같은 고성능 기능을 더욱 매끄럽게 구현할 수 있습니다. 데스크톱 애플리케이션 개발 시 초기에는 32비트의 범용성이 매력적일 수 있으나, 서비스의 규모가 커지고 현대적인 라이브러리를 지속적으로 활용해야 한다면 64비트 전환은 선택이 아닌 필수입니다. 안정적인 메모리 관리와 커뮤니티의 기술 지원을 받기 위해서는 하드웨어 표준에 맞춘 아키텍처 업데이트가 반드시 선행되어야 합니다.