제미나이로 정리한 기술 원고입니다.

긱뉴스에 올라온 글과 댓글, 지인들이 댓글에서 언급한 키워드를 중심으로 정리하라고 했더니 아래와 같은 글이 작성되었습니다. 정말 공부하기 좋은 세상입니다.!

 

리눅스 커널 변화와 PostgreSQL 성능 분석 보고서

1. 핵심 개념 상세 설명

SpinLock (스핀락)

스핀락은 임계 구역(Critical Section)에 진입하기 위해 대기하는 스레드가 잠들지 않고 루프를 돌며 권한을 획득할 때까지 계속 확인하는 동기화 기법

  • 동작 방식:
    1. 스레드가 자원 사용을 위해 락(Lock)을 요청합니다.
    2. 락이 이미 사용 중이라면, 스레드는 대기 큐로 들어가는 대신 CPU를 계속 사용하며(Spinning) 락의 해제 여부를 체크합니다.
    3. 락이 해제되는 순간 즉시 자원을 점유합니다.
  • 특징:
    • Context Switch 없음: 스레드가 잠들고 깨어나는 오버헤드가 없습니다.
    • 비결정적 대기: 대기 중에도 CPU를 100% 점유하므로, 대기가 길어지면 시스템 성능이 급락합니다.
  • 장단점:
    • 장점: 임계 구역이 매우 짧을 때, 잠들고 깨어나는 시간보다 그냥 기다리는 시간이 짧아 효율적입니다.
    • 단점: 락을 쥐고 있는 스레드가 지연되거나 선점(Preemption)당하면, 기다리는 모든 스레드가 CPU를 낭비하며 '미친 듯이 도는' 현상이 발생합니다.

rseq (Restartable Sequences)

리눅스 커널이 제공하는 최신 기술

유저스페이스 코드가 원자적(Atomic)으로 실행되어야 하는 구간에서 커널에 의해 중단(선점)되었는지를 감지

  • 도입 배경: 멀티 코어 환경에서 'CPU별 데이터(Per-CPU data)'에 접근할 때 락 없이 빠르게 처리하기 위해 고안되었습니다.
  • 동작 원리:
    1. 프로세스가 특정 코드 구간(rseq 구간)을 시작함을 커널에 알립니다.
    2. 만약 이 구간을 실행하다가 커널이 스레드를 중단시키고 다른 일을 시킨다면(Preemption), 커널은 이 사실을 기록합니다.
    3. 스레드가 다시 CPU를 잡았을 때, 구간 중간부터 이어서 하는 것이 아니라 구간의 처음으로 돌아가 다시 실행(Restart)합니다.
  • 효과: 락을 쓰지 않고도 데이터의 일관성을 유지할 수 있으며, 이번 PostgreSQL 사례처럼 "락을 쥐고 잠드는" 최악의 시나리오를 방지할 수 있는 대안으로 제시되었습니다.
비교 항목 SpinLock Mutex rseq / Lock-free
대기 방식 계속 회전 (CPU 소모) 잠듦 (CPU 반납) 재시도 또는 원자적 처리
권장 상황 매우 짧은 임계 구역 긴 대기 시간 예상 시 고성능 멀티코어 환경
오버헤드 낮음 (즉시 반응) 높음 (문맥 교환) 매우 낮음 (난이도 높음)

2. 커널 스케줄링 및 선점 (Preemption) 관련

리눅스 커널이 CPU 자원을 여러 프로세스에게 나누어 주는 방식에 대한 용어입니다.

  • 선점 (Preemption): 실행 중인 스레드의 의사와 상관없이 OS 스케줄러가 강제로 CPU 사용권을 뺏어 다른 스레드에게 주는 행위입니다.
  • PREEMPT_NONE: "나 건드리지 마" 모드입니다. 스레드가 스스로 I/O를 기다리거나 잠들기 전까지는 커널이 웬만해서는 실행을 중단시키지 않습니다. 서버 환경에서 처리량(Throughput)을 극대화할 때 유리합니다.
  • PREEMPT_FULL / LAZY: "언제든 비켜줄게" 모드입니다. 응답 속도(Latency)는 빨라지지만, 자주 흐름이 끊겨 전체적인 일 처리량은 줄어들 수 있습니다. 리눅스 7.0에서 NONE 모드가 사라지면서 이번 성능 저하의 도화선이 되었습니다.
  • Restartable Sequences (rseq): 커널이 유저스페이스(PostgreSQL 등)에 "방금 네 실행 흐름이 중간에 끊겼었어"라고 알려주는 기능입니다. 이를 통해 중단되었던 작업을 처음부터 다시 안전하게 실행할 수 있게 돕는 최신 기술입니다.

3. PostgreSQL 성능 회귀 사건 분석 (통합)

최근 리눅스 7.0(내부 커널 버전 기준)에서 발생한 이슈는 하드웨어와 OS, 소프트웨어의 설계 철학이 충돌한 대표적 사례입니다.

📍 사건의 발단: 커널 정책의 변화

  • PREEMPT_NONE 제거: 과거 서버용 리눅스의 기본값이었던 "자발적 양보 외엔 절대 끊지 않음" 모드가 사라졌습니다.
  • PREEMPT_LAZY 도입: 처리량을 고려하되 필요시 선점하는 절충안이 기본이 되었으나, PostgreSQL의 전역 스핀락 구조와 상극을 일으켰습니다.

📍 문제의 메커니즘 (Cascading Failure)

  1. 스핀락 점유: PostgreSQL의 백엔드 프로세스가 공유 버퍼 풀 접근을 위해 단일 전역 스핀락(s_lock)을 획득합니다.
  2. 마이너 페이지 폴트: 락을 쥔 상태에서 메모리에 접근했는데, 해당 페이지가 물리 램에 매핑되지 않아 커널이 개입합니다.
  3. 선점 발생: 페이지 폴트 처리 중, 커널이 스레드를 잠시 중단(Preemption)시킵니다.
  4. 폭주: 락을 쥐고 있는 놈이 자리를 비우자, 나머지 수백 개의 CPU 코어들이 락을 얻으려고 스핀락 루프를 돌며 CPU 점유율을 100%까지 끌어올립니다.

📍 해결책 및 트레이드오프

해결 방법 주요 내용 장점 단점
Huge Pages 메모리 페이지를 4KB → 2MB/1GB로 확대 페이지 폴트 발생 빈도 감소 메모리 사전 할당 필요
rseq 도입 선점 시 재시작 메커니즘 선점 병목 해소 대대적 코드 수정 필요
Direct I/O OS 캐시 우회 중복 캐싱 방지 성능이 케바케임

4. 용어 사전 요약

  • Direct IO
    • 운영체제(OS)의 페이지 캐시(Page Cache)를 거치지 않고 데이터베이스 엔진이 저장 장치(Disk)와 직접 데이터를 주고받는 방식
    • 보통 OS는 성능 향상을 위해 디스크에서 읽은 데이터를 메모리에 임시로 저장(캐싱)합니다. 하지만 DBMS는 자체적으로 더 정교한 버퍼 풀(Buffer Pool) 관리 시스템을 가짐
    • 중복 캐싱(OS와 DBMS가 같은 데이터를 이중으로 들고 있는 현상)을 방지하고 CPU 사용량을 줄일 수 있음. 다만 OD가 제공하는 최적화 기능을 사용하지 못하므로 DBMS가 데이터를 효율적으로 관리하지 못하면 성능이 떨어질 수 있음
  • 네할렘 (Nehalem):
    • 메모리 컨트롤러를 CPU 내부로 통합. 이로 인해 NUMA 구조를 확립한 아키텍처.
    • 2008년 말 인텔이 출시한 CPU 마이크로 아키텍처. 2009년 서버 시장에 보편화
  • SMP( ymmetric Multi-Processing, 대칭형 다중 처리):
    • 모든 CPU가 동일한 메모리 접근 권한을 갖는 대칭형 구조.
    • CPU 개수가 늘어날수록 메모리로 가는 통로(Bus)에 병목 현상이 생겨 성능 확장 어려움 -> NUMA 등장
  • NUMA( Non-Uniform Memory Access , 비균할 메모리 접근): 
    • CPU별 독립 메모리 구조. 멀티 소켓 서버의 표준. 네할렘 이후의 멀티 소켓 서버는 대부분 이 구조를 따름
    • 각 CPU(소켓)마다 자신만의 전용 메모리 영역을 가짐. 자기 옆 메모리 접근시에는 빠르지만, 다른 CPU에 붙은 메모리 접근하려면 버스로 주고 받아야 하므로 속도가 느려짐.
    • NUMA를 지원하지 않는 소프트웨어는 멀리 떨어진 메모리만 쓰다가 성능이 떨어질 수 있음.
  • CCIX(Cache Coherent Interconnect for Accelerators):
    • CPU와 가속기 (GPU, FPGA 등) 간 캐시 일관성을 유지하면서 아주 빠른 속도로 데이터를 주고받기 위한 차세대 인터페이스.
    • NUMA의 데이터 전송 속도 한계를 극복하고, 서로 다른 제조사의 칩들이 마치 하나의 메모리를 쓰는 것처럼(캐시 일관성) 통신
    • 최근에는 인텔이 주도하는 CXL(Compute Express Link) 기술이 이 분야의 대세가 되면서, NUMA가 가졌던 물리적 한계를 극복하는 메모리 풀링(Memory Pooling) 시대로 이행되고 있
  • TLB(Translation Lookaside Buffer):
    • 가상 주소를 실제 주소로 바꾸는 맵이 담긴 변환용 초고속 캐시. TLB가 작으면 자주 매핑 정보를 구성해야 해서 속도 느려짐. Huge Pages 사용 시 효율 급증.
  • Huge Page
    • 기본 메모리 페이지 크기를 4K에서 2M/1GB로 뻥튀기. 페이지 수가 줄어들어 페이지 폴트 발생횟수가 줄어들면서 TLB 효율이 높아짐.
  • 페이지 폴트(Page Fault)
    • 프로그램이 메모리 주소를 불렀는데, 실제 물리 램에 아직 준비가 안되었을 때 발생하는 이벤트
    • 커널은 급히 램을 할당해줘야 하므로 아주 미세한 지연이 발생.

5.  성능 분석 및 벤치마크 관련

  • TPS (Transactions Per Second): 초당 처리되는 트랜잭션 수로, DB의 심박수와 같습니다. 이번 사례에서는 9.8만에서 5만으로 반토막 났습니다.
  • pgbench: PostgreSQL의 성능을 측정하는 표준 도구입니다.
  • perf: 리눅스 커널의 성능 분석 도구입니다. CPU가 어떤 함수에서 시간을 가장 많이 쓰는지 초 단위로 쪼개서 보여줍니다. 이번 분석에서 s_lock(스핀락)이 CPU의 55%를 먹고 있다는 범인을 잡아냈습니다.

 

반응형

 

 

에피소드1. 불치하문( 不恥下問)

 

<<논어>>에 이런 구절이 있다.

子貢問曰:「孔文子何以謂之文也?」
子曰:「敏而好學,不恥下問,是以謂之文也。」

 

보통 다음과 같이 해석한다.

 

자공이 물었다. “위나라 공문자의 시호는 어떻게 문(文)이 되었습니까?”
공자께서 대답하시길 “공문자는 영민하면서도 배우기를 좋아했고, 아랫사람에게 묻기를 부끄러워하지 않았기 때문이다."

 

 

별 생각없이 그동안 ‘하(下)’를  아랫사람, 즉 부하 직원 정도로 이해하고 있었다. 그런데 오늘 문득 다른 의미가 떠올랐다. ‘하’는 단순히 지위가 낮은 사람이 아니라 연하(年下), 즉 나이가 어린 사람일 수도 있지 않을까 하는 생각이다.

그렇게 생각하면 이 구절의 의미는 조금 달라진다. “아랫사람에게 묻는 것을 부끄러워하지 않는다”가 아니라 “나이 어린 사람에게 묻는 것을 부끄러워하지 않는다.”가 된다. 조금 더 적극적으로 의역해 보자면 "배움에 나이가 무슨 상관이 있는가."로 해석될 수 있지 않을까?

나보다 어린 사람에게서도 배우는 것을 부끄러워하지 않는 태도. 배움에 집중하는 자세. 지금 시대에도 여전히 필요한 마음가짐이라 생각해 본다.



에피소드2. 쑥국

봄이라 동네 마트에서 쑥을 팔길래 사왔다. 쑥국을 한 번 직접 끓여 보면 알게 된다. 국을 끓이는 과정 자체는 의외로 간단하다.

문제는 쑥을 다듬는 과정이다.

흙을 털어내고, 시든 잎을 골라내고, 깨끗이 씻고, 잎 부분만 떼어내는데 생각보다 많은 시간이 들어간다. 하지만 이 과정을 대충 하게되면 국에서 흙맛이 난다. 국은 멀쩡한 것 같지만 어딘가 어색한 맛이 나고 부드러운 쑥의 느낌이 사라진다.

AI 서비스도 비슷한 면이 있다.
모델을 붙이고 기능을 만드는 일 자체는 생각보다(?) 빠르게 진행된다. 그러나 데이터를 준비하는 과정은 훨씬 오래 걸린다.

데이터를 모으고, 정리하고, 불필요한 것을 제거하고, 구조를 잡는 과정.. 이 과정을 반복하는 과정

이 과정을 충분히 거치지 않으면 결과도 어딘가 이상해진다. 마치 흙이 제대로 씻기지 않은 쑥으로 끓인 쑥국처럼.

AI 서비스의 품질은 결국 모델이 아니라 데이터 준비 과정에서의 정성에 따라 갈린다는 생각을 종종 하게 되는 요즘이다.

반응형

+ Recent posts