Articleopenai.com·2026년 6월 30일·0

Core dump epidemiology: fixing an 18-year-old bug

Quick Summary

OpenAI는 Rockset의 불가해한 충돌을 개별 코어 덤프가 아닌 전체 충돌 집단의 상관관계로 분석해, 하나처럼 보였던 현상이 불량 하드웨어와 GNU libunwind의 18년 된 경쟁 조건이라는 두 독립 문제였음을 밝혀냈다.

Core dump epidemiology: fixing an 18-year-old bug 관련 대표 이미지

🖼️ 인포그래픽

Core dump epidemiology: fixing an 18-year-old bug 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

Core dump epidemiology: fixing an 18-year-old bug 내용을 설명하는 본문 이미지

💡 한 줄 요약

OpenAI는 Rockset의 불가해한 충돌을 개별 코어 덤프가 아닌 전체 충돌 집단의 상관관계로 분석해, 하나처럼 보였던 현상이 불량 하드웨어와 GNU libunwind의 18년 된 경쟁 조건이라는 두 독립 문제였음을 밝혀냈다.

📌 핵심 요약

  • Rockset의 C++ 실행 계층에서 함수 반환 시 저장된 반환 주소가 NULL이 되거나 스택 포인터가 8바이트 어긋난 상태로 충돌하는 비정상적인 현상이 관찰됐다.
  • 초기에는 일부 코어 덤프를 정밀 조사하며 하나의 소프트웨어 결함을 찾으려 했지만, 손상된 스택 추적과 방대한 후보 범위 때문에 신뢰할 수 있는 설명이나 전체 사례 집합을 만들지 못했다.
  • 조사팀은 개별 사례를 진단하는 방식에서 벗어나 지난 1년간의 모든 프로덕션 코어 덤프를 자동 분류하고 인프라 정보와 연계하는 역학적 접근법으로 전환했다.
  • 분석 결과 반환 주소가 NULL인 충돌은 여러 지역과 클러스터에 분산된 반면, 스택 정렬 오류는 특정 지역·시작 시점·물리 호스트에 집중돼 별도의 하드웨어 문제로 판명됐다.
  • 문제의 물리 호스트를 제외하자 스택 정렬 오류가 사라졌으며, 글은 나머지 충돌 원인이 널리 사용되는 GNU libunwind의 18년 된 경쟁 조건이었다고 밝힌다.

🧩 주요 포인트

  1. Rockset의 C++ 실행 계층에서 함수 반환 시 저장된 반환 주소가 NULL이 되거나 스택 포인터가 8바이트 어긋난 상태로 충돌하는 비정상적인 현상이 관찰됐다.
  2. 초기에는 일부 코어 덤프를 정밀 조사하며 하나의 소프트웨어 결함을 찾으려 했지만, 손상된 스택 추적과 방대한 후보 범위 때문에 신뢰할 수 있는 설명이나 전체 사례 집합을 만들지 못했다.
  3. 조사팀은 개별 사례를 진단하는 방식에서 벗어나 지난 1년간의 모든 프로덕션 코어 덤프를 자동 분류하고 인프라 정보와 연계하는 역학적 접근법으로 전환했다.
  4. 분석 결과 반환 주소가 NULL인 충돌은 여러 지역과 클러스터에 분산된 반면, 스택 정렬 오류는 특정 지역·시작 시점·물리 호스트에 집중돼 별도의 하드웨어 문제로 판명됐다.
  5. 문제의 물리 호스트를 제외하자 스택 정렬 오류가 사라졌으며, 글은 나머지 충돌 원인이 널리 사용되는 GNU libunwind의 18년 된 경쟁 조건이었다고 밝힌다.

🧠 상세 정리

1. 확장 가능한 검색 인프라와 C++의 위험

OpenAI의 모델과 에이전트는 질문에 답하거나 작업을 수행하는 추론 시점에 관련 정보를 검색하기 위해 확장 가능한 데이터 인프라에 점점 더 의존한다. 그 핵심 구성 요소 중 하나인 Rockset은 실시간 분석과 검색을 위한 클라우드 네이티브 데이터 시스템이며, 동기화 커넥터와 대화 검색 등 여러 내부 용도에 사용된다. Rockset의 실행 계층은 성능을 높이고 메모리 사용량을 줄이기 위해 C++로 작성됐지만, 이 언어의 메모리 안전성 부족은 잘못된 주소에 쓰거나 접근하는 버그가 프로세스 충돌로 이어질 수 있음을 뜻한다. OpenAI는 충돌 시 folly의 치명적 신호 처리기로 스택 추적을 기록하고, 당시 프로세스 상태를 담은 코어 덤프를 Azure Blob Storage에 올려 사후 분석한다. 쿼리 처리 노드가 복제돼 있어 개별 충돌이 고객에게 미치는 영향은 줄어들지만, 신뢰성과 품질 목표를 충족하려면 각각의 세그멘테이션 오류 원인을 찾아 수정해야 했다.

2. 함수 반환 순간에 나타난 불가능해 보이는 충돌

조사의 출발점은 평범한 C++ 함수가 실행을 마친 뒤 실행 코드가 아닌 엉뚱한 주소로 반환하면서 커널에 의해 중단되는 일련의 Rockset 충돌이었다. 일부 코어에서는 스택 프레임에 저장된 반환 주소가 NULL이었고, 다른 코어에서는 CPU의 스택 포인터 레지스터인 %rsp가 정상 위치에서 8바이트 어긋나 있었다. 저장된 반환 주소 하나만 정확히 덮는 우발적 메모리 쓰기는 가능하지만 매우 드물며, 인라인 어셈블리·setcontext·longjmp 없이 정상 실행 중 %rsp만 8바이트 바뀌는 현상은 더욱 설명하기 어려웠다. 컴파일된 코드는 통상 함수 프롤로그와 에필로그에서만 스택 포인터를 직접 조정하기 때문에 관찰된 상태는 일반적인 애플리케이션 결함의 형태와 맞지 않았다. 조사팀과 ChatGPT가 검토한 여러 가설에는 각각 강한 반증이 있었고, 결국 처음에는 하나라고 생각했던 증상이 실제로는 서로 무관한 두 종류의 문제였다는 사실이 드러났다.

3. 개별 코어 덤프에 매달린 첫 번째 조사

초기 접근법은 전통적인 디버깅과 같았다. 조사팀은 DocumentTree::updateDocument에서 발생한 몇 개의 코어 덤프를 깊이 들여다보고, 함수가 알 수 없는 함수 X를 호출한 동안 스택이 손상된 뒤 X가 유효하지 않은 주소로 반환했다는 가설을 세웠다. 그러나 updateDocument는 규모가 크고 많은 코드가 인라인화되므로 X가 될 수 있는 후보가 지나치게 많았으며, 스택이 언제 손상됐는지도 알 수 없었다. 자체 C++ 코드, 컴파일러나 링크 과정, 런타임 라이브러리, 리눅스의 신호 전달 또는 문맥 전환, 드문 하드웨어 이상까지 검토해야 했지만 명확한 방향은 나오지 않았다. 애플리케이션 로그의 스택 추적 자체가 손상되거나 누락돼 텍스트 검색은 거짓 양성과 거짓 음성을 동시에 만들었고, 코어를 수작업으로 더 확인하는 방법도 전체 사례를 신뢰성 있게 수집하기에는 노동 비용이 너무 컸다.

4. 잘못된 단일 원인 가정과 하드웨어 조기 배제

조사팀은 충돌이 여러 지역과 여러 하드웨어 유형에서 보인다는 이유로 초기에 하드웨어 결함을 잘못 배제하고 소프트웨어 원인만 찾았다. 이어 며칠 동안 %rsp가 어긋난 코어 하나를 골라 스택과 레지스터 내용을 바탕으로 충돌 직전의 실행 이력을 재구성했으며, 몇 가지 단서는 얻었지만 결정적인 설명에는 도달하지 못했다. 특히 서로 다른 충돌이 모두 동일한 원인에서 나왔다는 전제를 버리지 못했기 때문에, 한 집단에서 얻은 반례가 다른 집단의 가설까지 무너뜨리는 상황이 반복됐다. 이 방식은 개별 사례의 세부 상태를 복원하는 데는 유용했지만, 발생 지역·시점·장비와 같은 전체 분포의 특징을 보여주지 못했다. 결과적으로 문제를 깊게 보는 것만으로는 충분하지 않았으며, 어떤 사례들이 실제로 같은 증후군에 속하는지를 먼저 구분할 데이터가 필요했다.

5. 프레임 포인터와 레드 존이 보존한 충돌 전 흔적

코어 덤프에서 충돌 이전 상태를 읽을 수 있었던 배경에는 Rockset의 컴파일 설정과 x86_64 호출 규약이 있었다. Rockset은 -fno-omit-frame-pointer로 컴파일되므로 활성 스택 프레임을 %rbp를 통해 찾을 수 있고, 호출자들은 프레임 포인터의 연결 목록으로 이어진다. AMD64 System V ABI는 %rsp 아래 128바이트를 레드 존으로 예약하며, 리눅스 커널은 신호를 전달할 때 이 영역을 덮어쓰지 않기로 약속한다. SIGSEGV가 발생하면 folly의 치명적 신호 처리기가 충돌한 스레드의 스택에서 실행돼 이미 반환한 함수의 프레임을 덮어쓸 수 있지만, 마지막 128바이트에 남은 일부 흔적은 보존된다. 이 때문에 조사팀은 막 제거된 X의 스택 프레임이 반환 주소만 NULL인 채 나머지는 정상처럼 보였다고 판단할 수 있었고, 간단한 함수들만 관련된 사례에서는 %rsp가 실행 중 어긋난 뒤에도 여러 호출이 성공하다가 최종 반환 시점에야 충돌했다는 정황까지 확인했다.

6. 빈번한 신호 사용과 커널 가설의 검토

Rockset은 다수의 경량 작업을 하나의 스레드 풀에서 다중화하기 때문에 일반적인 프로그램보다 신호를 적극적으로 사용한다. 쿼리별 CPU 시간을 저렴하게 추정하기 위해 coarse_thread_cputime_clock이라는 방식을 사용하며, timer_create로 CPU 시간이 몇 밀리초 누적될 때마다 SIGUSR2를 전달해 스레드 로컬 값을 갱신한다. 개별 작업은 실행 중 거친 시계가 전진하는 모습을 보지 못할 수도 있지만, 모든 델타를 합하면 실제 쿼리 CPU 시간의 편향되지 않은 추정치를 얻을 수 있다. 이처럼 신호가 매우 자주 전달되는 환경에서는 문맥 전환이나 신호 처리 과정의 희귀한 커널 결함이 그럴듯한 후보로 보였다. 조사팀은 관련 버그 보고서, 커널 소스, Azure 전용 커널 패치를 읽고 스트레스 테스트도 수행했지만, 관찰된 충돌과 연결되는 근거는 찾지 못했고 결국 다른 조사 전략이 필요하다고 판단했다.

7. 의사식 진단에서 역학적 분석으로의 전환

전환점은 한 환자의 검사 결과를 깊게 파고드는 의사식 접근과 전체 환자 집단의 패턴을 찾는 역학적 접근을 구분한 데서 나왔다. 조사팀은 특정 릴리스 이후 문제가 시작됐는지, 특정 하드웨어 SKU·지역·커널 버전과 연관되는지, 하나의 증상처럼 보이는 데이터 안에 여러 군집이 숨어 있는지를 묻기 시작했다. 로그 텍스트만으로는 전체 사례를 정확히 식별할 수 없었으므로, 정보가 더 풍부한 코어 덤프를 자동 분석하는 파이프라인을 구축했다. ChatGPT가 작성한 스크립트는 각 코어 파일의 앞부분을 내려받아 레지스터를 추출하고, 로그를 이용해 알려진 거짓 양성을 제거한 뒤 충돌을 반환 주소 NULL, 스택 정렬 오류, 기타 유형으로 분류했다. 이 스크립트를 지난 1년간의 모든 프로덕션 Rockset 코어 덤프에 병렬 실행하자 수작업 조사에서는 볼 수 없었던 깨끗한 모집단 데이터가 만들어졌고, 서로 다른 두 충돌 집단의 상관관계가 즉시 나타났다.

8. 두 충돌 집단의 분리와 불량 호스트 격리

반환 주소가 NULL인 코어는 여러 클러스터와 지역에 넓게 분포했고 최근 빈도가 증가했지만, 명확한 시작일이나 단일 인프라 경계는 없었다. 반면 스택 정렬 오류는 모두 한 지역에서 발생했고 시작 시점이 뚜렷했으며, 오래 실행된 노드에서는 한 번도 나타나지 않았다. 여러 Azure 가상 머신에서 관찰됐음에도 이러한 분포는 같은 물리 장비에 차례로 배치된 VM들이 하나의 불량 하드웨어 영향을 받았다는 설명과 부합했다. 조사팀은 Kubernetes 노드와 타임스탬프의 정제된 목록을 이용해 모든 스택 정렬 오류를 단일 물리 호스트로 추적하고 해당 호스트를 사용 금지 목록에 올렸다. 수주간의 스트레스 테스트로 레지스터 손상을 통제된 환경에서 재현하지는 못했지만, 호스트를 서비스에서 제거한 뒤 관련 충돌이 사라졌으며 조사팀은 유사 문제가 재발할 때 더 쉽게 감지하고 처리하도록 치명적 신호 처리기의 정보 수집을 개선하기 시작했다.

🧾 핵심 주장 / 시사점

  • 서로 모순되는 증거가 계속 나올 때는 하나의 복잡한 원인을 억지로 찾기보다, 관찰 집단 안에 독립적인 장애 군집이 섞여 있는지 먼저 확인해야 한다.
  • 손상된 로그만으로 충돌을 분류하기 어려운 경우 코어 덤프의 레지스터·스택 정보와 지역·노드·시점 같은 운영 메타데이터를 결합하면 개별 사례에서는 보이지 않는 상관관계를 찾을 수 있다.
  • 재현에 실패했더라도 특정 인프라 요소의 격리 전후로 장애가 사라지는지를 전체 데이터로 검증하면 실질적인 원인 분리와 완화가 가능하며, 이후에는 재발을 빠르게 식별할 관측 기능을 강화해야 한다.

✅ 액션 아이템

  • 개별 코어 덤프 수작업 분석을 중단하고, 1년치 프로덕션 코어 덤프를 자동 분류해 지역·클러스터·호스트별 충돌 패턴을 우선 산출한다.
  • 반환 주소 NULL 충돌과 스택 포인터 8바이트 정렬 오류를 인프라 속성으로 분리해 특정 호스트 제외 전후의 잔존율을 비교하고 원인군을 구분한다.
  • 하드웨어 격리 후 남은 충돌 건수만을 추려 GNU libunwind 경쟁 조건 구간을 별도 후보로 분류해 수정 대상 우선순위를 정한다.

❓ 열린 질문

  • 두 현상이 독립 이슈인지 어떻게 판별하면 상호 간의 경계 오판을 줄일 수 있는가?
  • 손상된 스택 추적이 반복되면 어떤 자동 분류 보정이 허위 음성 누락을 줄일 수 있을까?
  • GNU libunwind 경쟁 조건 완화 시 어떤 변경 범위를 먼저 적용하면 반환 주소 NULL 패턴 악화를 피할 수 있을까?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.