Rust 메모리 누수 문제 해결 여행을 기억하세요 경험 요약 |

특정 연속 스트레스 테스트 중에 요청 볼륨이 안정적인 경우에도 OOM에 의해 종료될 때까지 GreptimeDB의 프런트엔드 노드 메모리가 계속 증가하는 것을 발견했습니다. 우리는 프런트엔드에 메모리 누수가 발생해야 한다고 판단하여 메모리 누수 문제를 해결하기 위한 여정을 시작했습니다.

힙 프로파일링

대규모 프로젝트에서는 코드만 보는 것만으로는 메모리 누수를 찾는 것이 거의 불가능합니다. 따라서 먼저 프로그램의 메모리 사용량에 대한 통계 분석을 수행해야 합니다. 다행히 GreptimeDB에서 사용하는 jemalloc에는 힙 프로파일링이 포함되어 있으며 jemalloc 프로파일 덤프 파일 내보내기 도 지원합니다 . 그래서 GreptimeDB의 Frontend 노드의 메모리가 300MB와 800MB에 도달했을 때, 우리는 각각의 메모리 프로필 파일을 덤프한 다음 jemalloc의 내장 도구를 사용하여 둘 사이의 jeprof메모리 차이( --base매개변수)를 분석하고 마지막으로 이를 Flame 그래프로 표시했습니다.

분명히 사진 중앙의 긴 블록은 점점 늘어나는 500MB의 메모리가 차지하고 있습니다. 자세히 살펴보면 스레드 관련 스택 추적이 있습니다. 스레드가 너무 많이 생성된 것이 아닐까요? 간단히 ps -T -p명령어를 이용해 Frontend 노드의 프로세스를 여러 차례 확인해보았는데, 스레드 수는 84개로 안정적이었고, 모두 생성될 것으로 예상되는 스레드들이었습니다. 따라서 "쓰레드가 너무 많은" 이유를 제거할 수 있습니다.

더 자세히 살펴보면 Tokio 런타임과 관련된 많은 스택 추적을 발견했으며 Tokio 작업 누수 또한 일반적인 메모리 누수입니다. 이번에는 또 다른 아티팩트인 Tokio-console을 사용하겠습니다 .

토키오 콘솔

Tokio Console은 Tokio의 공식 진단 도구입니다. 출력 결과는 다음과 같습니다.

실제로 5559개의 작업이 실행 중이며 대부분이 유휴 상태임을 알 수 있습니다! 그러면 Tokio의 작업에서 메모리 누수가 발생하는 것을 확인할 수 있습니다. 이제 질문은 다음과 같습니다. GreptimeDB 코드에서 종료할 수 없을 만큼 많은 Tokio 작업이 생성된 곳은 어디입니까?

위 그림의 "위치" 열에서 작업이 생성되는 위치를 확인할 수 있습니다 .

impl Runtime {
    /// Spawn a future and execute it in this thread pool
    ///
    /// Similar to Tokio::runtime::Runtime::spawn()
    pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        self.handle.spawn(future)
    }
}

다음 작업은 이 메서드를 호출하는 GreptimeDB의 모든 코드를 찾는 것입니다.

..Default::default()

코드를 면밀히 검사한 후 마침내 Tokio 작업이 누출된 위치를 찾아 PR #1512 에서 누출을 수정했습니다 . 간단히 말해서, 자주 생성되는 구조체의 생성자에서 백그라운드에서 계속 실행할 수 있는 Tokio 작업을 생성했지만 제때에 재활용하지 못했습니다. 리소스 관리의 경우 생성자 자체에서 작업을 생성하는 것은 문제가 되지 않습니다. 단, Drop에서 작업이 성공적으로 종료될 수 있어야 합니다. 메모리 누수의 나쁜 점은 이 규칙을 무시했다는 것입니다.

이 생성자는 Default::default()구조체의 메서드에서도 호출되므로 근본 원인을 찾기가 더 어렵습니다.

Rust에는 다른 구조체, 즉 " Struct Update Syntax " 를 사용하여 자신만의 구조체를 구성하는 매우 편리한 방법이 있습니다 . 구조체가 구현되면 Default구조체의 필드 생성자에서 간단히 사용할 수 있습니다 ..Default::default(). Default::default()내부에 "부작용"이 있는 경우 (예를 들어 이번에 메모리 누수가 발생한 이유 - 백그라운드에서 실행 중인 Tokio 작업 생성) 다음 사항에 특별한 주의를 기울여야 합니다. 구조체 생성이 완료된 후 Default임시 구조체가 생성됩니다. 폐기됩니다. 자원 재활용을 잘 하세요.

예를 들어, 다음과 같은 작은 예가 있습니다: (Rust Playground )

struct A {
    i: i32,
}

impl Default for A {
    fn default() -> Self {
        println!("called A::default()");
        A { i: 42 }
    }
}

#[derive(Default)]
struct B {
    a: A,
    i: i32,
}

impl B {
    fn new(a: A) -> Self {
        B {
            a,
            // A::default() is called in B::default(), even though "a" is provided here.
            ..Default::default()
        }
    }
}

fn main() {
    let a = A { i: 1 };
    let b = B::new(a);
    println!("{}", b.a.i);
}

구조체 A의 메서드 default가 호출되어 인쇄됩니다 called A::default().

요약하다

  • Rust 프로그램의 메모리 누수 문제를 해결하려면 jemalloc의 힙 프로파일링을 사용하여 덤프 파일을 내보낸 다음 Flame 그래프를 생성하여 메모리 사용량을 시각적으로 표시할 수 있습니다.
  • Tokio-console은 증가하는 유휴 작업에 특히 주의하여 Tokio 런타임의 작업 실행 상태를 쉽게 표시할 수 있습니다.
  • 일반적으로 사용되는 구조체의 생성자에 부작용이 있는 코드를 남기지 마세요.
  • Default값 유형 구조체에만 사용해야 합니다.

Greptime 소개

Greptime Greptime Technology는 2022년에 설립되었으며 현재 시계열 데이터베이스 GreptimeDB와 GreptimeCloud라는 두 가지 제품을 개선 및 구축하고 있습니다.

GreptimeDB는 Rust 언어로 작성된 시계열 데이터베이스로, 분산형, 오픈 소스, 클라우드 기반, 호환성이 뛰어납니다. 이는 기업이 시계열 데이터를 실시간으로 읽고, 쓰고, 처리하고 분석하는 데 도움이 되며, 장기적으로 비용을 절감할 수 있습니다. 저장.

오픈 소스 GreptimeDB를 기반으로 하는 GreptimeCloud는 사용자에게 완전 관리형 DBaaS는 물론 관찰 가능성, 사물 인터넷 및 기타 분야와 결합된 애플리케이션 제품을 제공합니다. 클라우드를 사용하여 소프트웨어와 서비스를 제공하면 신속한 셀프 서비스 프로비저닝 및 제공, 표준화된 운영 및 유지 관리 지원, 더 나은 리소스 유연성을 얻을 수 있습니다. GreptimeCloud가 내부 테스트를 위해 공식적으로 오픈했습니다. 최신 개발 소식을 보려면 공식 계정이나 공식 웹사이트를 팔로우하세요!

공식 홈페이지: https://greptime.com/

공개 계정: GreptimeDB

GitHub: https://github.com/GreptimeTeam/greptimedb

문서: https://docs.greptime.com/

트위터: https://twitter.com/Greptime

슬랙: https://greptime.com/slack

링크드인: https://www.linkedin.com/company/greptime/

1990년대에 태어난 프로그래머가 비디오 포팅 소프트웨어를 개발하여 1년도 안 되어 700만 개 이상의 수익을 올렸습니다. 결말은 매우 처참했습니다! 고등학생들이 성인식으로 자신만의 오픈소스 프로그래밍 언어 만든다 - 네티즌 날카로운 지적: 만연한 사기로 러스트데스크 의존, 가사 서비스 타오바오(taobao.com)가 가사 서비스를 중단하고 웹 버전 최적화 작업 재개 자바 17은 가장 일반적으로 사용되는 Java LTS 버전입니다. Windows 10 시장 점유율 70%에 도달, Windows 11은 계속해서 Open Source Daily를 지원합니다. Google은 Docker가 지원하는 오픈 소스 Rabbit R1을 지원합니다. Electric, 개방형 플랫폼 종료 Apple, M4 칩 출시 Google, Android 범용 커널(ACK) 삭제 RISC-V 아키텍처 지원 Yunfeng은 Alibaba에서 사임하고 향후 Windows 플랫폼용 독립 게임을 제작할 계획
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/6839317/blog/11044292