왜 Distributed Query Engines는 실행 레이어에서 복잡성을 항상 축적하는가

발행: (2025년 12월 27일 오전 11:06 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL, 마크다운 형식 등은 그대로 유지됩니다.)

실행 레이어는 “그저 실행”이 아니다

아키텍처 다이어그램은 종종 쿼리 엔진을 깔끔한 상자들로 나눕니다:

  • SQL 파싱
  • 최적화
  • 계획 생성
  • 실행
  • 저장소

이러한 분해는 중요한 진실을 숨깁니다:

실행 이전의 모든 것은 대부분 정적입니다.

실행 레이어는 현실이 드러나는 곳입니다.

현실은 다음과 같습니다:

  • 데이터 편향
  • 느리거나 실패하는 노드
  • 네트워크 지터
  • 규모가 커질 때만 나타나는 메모리 압박

잘못된 것으로 밝혀지는 모든 가정은 결국 실행 레이어에 도착합니다. 그래서 최적화기는 오래 지속되는 반면, 실행 레이어는 지속적으로 재구성됩니다.

Shuffle: 시스템 전반에 걸친 조용한 비용 배가기

Shuffle은 보통 “데이터를 다시 파티션하는 것일 뿐”이라고 소개됩니다. 실제로 Shuffle은 동시에 다음을 압박합니다:

  • 네트워크 대역폭
  • 메모리(종종 급격한 스파이크 발생)
  • 디스크 I/O(대체 수단)
  • CPU(해싱, 정렬, 직렬화)

더 중요한 것은 Shuffle이 불확실성을 증폭한다는 점입니다:

  • 하나의 느린 노드가 전역 병목이 된다
  • 약간의 데이터 스키우가 OOM으로 이어진다
  • 작은 네트워크 지터가 쿼리 전체 지연으로 변한다

많은 운영 사고가 Shuffle에 기인합니다—처음에는 명확하지 않았더라도.

동시성: 추가는 쉬워도 제어는 어려워

A common early belief is:

“더 많은 병렬성은 더 나은 성능을 의미한다.”

Typical mistakes:

  • async를 CPU‑바운드 연산자에 everywhere 사용
  • 비동기 런타임과 수동 스레드 풀을 혼합
  • 역압력(backpressure) 없이 과도한 작업 스폰

The results:

  • 예측 불가능한 스케줄링
  • 증가된 꼬리 지연
  • 이해하기 어려운 동작

Once concurrency leaks into operator design, fixing it later usually means rewriting core abstractions. → 일단 동시성이 연산자 설계에 스며들면, 나중에 수정하려면 보통 핵심 추상화를 다시 작성해야 한다.

Rust는 메모리 손상을 방지하지만 — 메모리 서프라이즈는 방지하지 않는다

Rust는 메모리 안전성에 뛰어납니다. 하지만 보장하지 않는 것들은:

  • 예측 가능한 메모리 사용량
  • 중간 데이터에 대한 제한된 수명
  • Shuffle 또는 Join 시 안정적인 메모리 피크

대부분의 실행 레이어 메모리 오류는 누수가 아니라:

  • 버퍼가 예상보다 오래 유지됨
  • 수명이 단계 간에 의도치 않게 연장됨
  • 실제 워크로드에서만 나타나는 메모리 급증

이러한 문제는 초기에 발견하기 어렵고, 나중에 수정하는 비용이 많이 듭니다.

왜 실행 레이어 설계가 자주 다시 쓰여지는가

시스템 전반에 걸쳐 동일한 실패 패턴이 계속 나타납니다.

❌ 모호한 실행 모델

초기 설계에서는 쿼리를 다음과 같이 취급하곤 합니다:

  • SQL 문자열
  • 혹은 느슨하게 정의된 단계들의 순서

후에 팀은 “실행 계획”, 연산자 그래프, 스케줄러 등을 “추가”하려고 합니다. 특히 Rust와 같은 강타입 시스템에서는 이것을 구제하기가 거의 불가능합니다. 실행 의미론은 처음부터 명시적이어야 합니다.

❌ 공유 가변 상태

Arc는 초기 진행을 쉽게 만들지만, 규모가 커지면 다음과 같은 문제를 초래합니다:

  • 락 경쟁
  • 지연 시간 진동
  • 비동기 컨텍스트에서의 교착 상태 위험

데이터가 흐르는 방식으로 설계된 실행 레이어가 공유된 상태보다 훨씬 잘 작동합니다.

❌ 셔플을 최적화 문제로 취급

많은 팀이 셔플을 다음과 같이 튜닝해서 없앨 수 있다고 가정합니다:

  • 파티션 수 증가
  • 더 스마트한 해싱
  • 더 나은 캐싱

실제로 셔플에는 물리적인 하한이 존재합니다. 가장 효과적인 최적화는 종종 셔플 자체를 피하는 것입니다.

❌ 흐릿한 오류 경계

다음 사이에 명확한 구분이 없으면:

  • 작업 수준 실패
  • 단계 수준 실패
  • 쿼리 수준 실패

시스템은 취약해집니다. 패닉과 전역 재시도는 분산 실행에서 확장되지 않습니다.

Hard‑Won Engineering Consensus

  • 가능한 한 Shuffle을 피한다
  • 피크 처리량보다 예측 가능성을 선호한다
  • 실행 안정성을 일급 관심사로 다룬다
  • 많은 “최적화”가 실제로는 손상 방지라는 것을 받아들인다

실행 레이어는 연산자를 빠르게 실행하는 것이 아니다. 불확실성을 관리하는 것이다.

최종 생각

If your distributed query engine keeps getting more complex in the Execution Layer — if you’re refactoring, redesigning, and questioning earlier decisions — that’s usually not a failure. It means the system has left the idealized world and entered reality:

  • 실제 데이터
  • 실제 네트워크
  • 실제 머신

Execution‑layer complexity is the cost of operating in the real world.

If you’ve built or operated query engines, I’d love to hear how these issues showed up in your system.

Back to Blog

관련 글

더 보기 »

Knotlog: PHP용 와이드 로깅

왜 로깅이 형편없는가와 이를 고치는 방법 loggingsucks.com(https://loggingsucks.com/)이 훌륭하게 설명하듯, 전통적인 로깅은 현대 환경에 대해 근본적으로 깨져 있다.