처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.03.24 48

PostgreSQL에서 Ctrl+C가 쿼리를 취소하는 방식은 놀라울 정도로 해키하다

Hacker News 원문 보기
PostgreSQL에서 Ctrl+C가 쿼리를 취소하는 방식은 놀라울 정도로 해키하다

터미널에서 Ctrl+C를 누르면 무슨 일이 일어날까?

대부분의 터미널 프로그램에서 Ctrl+C를 누르면 현재 실행 중인 프로세스에 SIGINT 시그널이 전달되고, 프로세스가 종료됩니다. 간단하고 직관적이죠. 그런데 PostgreSQL의 CLI 도구인 psql에서 Ctrl+C를 누르면, 프로세스가 종료되는 것이 아니라 현재 실행 중인 쿼리만 취소됩니다. 장시간 걸리는 쿼리를 실수로 실행했을 때 매우 편리한 기능인데, 이것이 내부적으로 어떻게 구현되어 있는지 들여다보면 상당히 놀랍습니다.

Neon의 엔지니어링 블로그에서 이 메커니즘을 깊이 파헤쳤는데, 그 구현 방식이 "해키하다(hack-y)"고 표현한 데는 충분한 이유가 있습니다.

일반적인 시그널 처리와는 다르다

보통 프로그램이 Ctrl+C를 처리하는 방식은 이렇습니다. SIGINT 핸들러를 등록하고, 시그널을 받으면 현재 작업을 중단하고 정리(cleanup) 코드를 실행한 뒤 적절히 종료하거나 다음 입력을 기다립니다. 프로세스 내부에서 모든 것이 처리됩니다.

psql이 다른 이유는, 쿼리 실행이 psql 프로세스 내부에서 일어나는 것이 아니기 때문입니다. psql은 SQL 쿼리를 PostgreSQL 서버 프로세스에 전송하고, 서버가 그 쿼리를 실행합니다. psql은 단지 결과를 기다리고 있을 뿐입니다. 그래서 psql 프로세스에 SIGINT를 보내는 것만으로는 서버에서 실행 중인 쿼리를 중단시킬 수 없습니다.

실제 구현: 새로운 TCP 연결을 여는 방식

psql이 실제로 하는 일은 이렇습니다. Ctrl+C 시그널을 받으면, psql은 서버에 대한 완전히 새로운 TCP 연결을 열고, 그 연결을 통해 CancelRequest라는 특별한 메시지를 서버에 보냅니다. 이 메시지에는 원래 연결의 프로세스 ID와 시크릿 키가 포함되어 있어서, 서버가 어떤 쿼리를 취소해야 하는지 식별할 수 있습니다.

왜 기존 연결을 통해 취소 요청을 보내지 않을까요? 이유는 PostgreSQL의 프로토콜이 반이중(half-duplex) 방식이기 때문입니다. 클라이언트가 쿼리를 보내면, 서버가 응답을 보낼 때까지 해당 연결은 서버 쪽에서 "쓰기" 모드입니다. 클라이언트가 그 연결로 새로운 메시지를 보낼 수 없습니다. 서버는 결과를 보내느라 바쁘고, 클라이언트의 추가 입력을 읽지 않습니다.

이것은 HTTP/1.1의 파이프라이닝 제약과 비슷합니다. 요청을 보내고 응답을 받기 전에 같은 연결로 새 요청을 보내는 것이 프로토콜 설계상 어렵기 때문에, 별도의 채널(연결)을 사용하는 것입니다.

시크릿 키 메커니즘

보안 관점에서, 아무나 다른 사람의 쿼리를 취소할 수 있으면 안 됩니다. 그래서 PostgreSQL은 연결이 수립될 때 서버가 클라이언트에게 백엔드 키(backend key)를 전달합니다. 이 키는 랜덤으로 생성된 32비트 정수인데, CancelRequest를 보낼 때 이 키를 포함해야 합니다. 서버는 키가 일치하는 경우에만 쿼리를 취소합니다.

다만 이 보안 메커니즘이 완벽하지는 않습니다. 32비트라는 것은 약 43억 가지 경우의 수밖에 안 되고, 네트워크 상에서 패킷을 가로챌 수 있다면 키를 탈취할 수 있습니다. 물론 실제 운영 환경에서는 SSL/TLS로 연결을 암호화하기 때문에 큰 문제는 아니지만, 프로토콜 설계 관점에서는 현대적인 기준에 미치지 못한다는 평가도 있습니다. PostgreSQL 17에서는 이 부분이 개선되어 더 긴 시크릿 키를 사용하도록 변경되었습니다.

취소가 보장되지 않는다는 사실

더 놀라운 점은, CancelRequest를 보내더라도 쿼리가 반드시 취소되리라는 보장이 없다는 것입니다. PostgreSQL 공식 문서에도 명시되어 있듯, 취소 요청은 "최선을 다해(best effort)" 처리됩니다. 서버가 이미 결과 생성을 완료했거나, 취소 불가능한 상태에 있으면 취소되지 않습니다. 심지어 네트워크 문제로 CancelRequest 자체가 서버에 도달하지 못할 수도 있습니다.

이런 비결정적인 동작은 현대적인 소프트웨어 설계 원칙과는 거리가 있습니다. 하지만 PostgreSQL의 역사를 생각하면 이해가 됩니다. 이 프로토콜은 1990년대에 설계되었고, 당시의 네트워크 프로그래밍 관행과 제약 조건 속에서 만들어진 실용적인 해결책이었습니다. 그리고 30년이 지난 지금도 "충분히 잘 작동하기" 때문에 근본적인 변경 없이 유지되고 있습니다.

시그널 핸들러 내부의 위험한 코드

Neon의 글에서 특히 소름 끼치는 부분은, psql의 SIGINT 핸들러 내부에서 새로운 TCP 연결을 열고 데이터를 보내는 코드가 실행된다는 점입니다. 시그널 핸들러 안에서는 async-signal-safe한 함수만 호출해야 한다는 것이 POSIX의 규칙입니다. malloc, printf, 소켓 함수 등 대부분의 표준 라이브러리 함수는 시그널 핸들러에서 안전하지 않습니다.

psql이 시그널 핸들러 안에서 네트워크 I/O를 수행하는 것은 기술적으로 정의되지 않은 동작(undefined behavior)을 유발할 수 있습니다. 실제로 이로 인한 교착 상태(deadlock)나 크래시가 보고된 적이 있다고 합니다. 동작하는 것이 신기할 정도의 구현인데, 수십 년간 대부분의 경우 문제없이 작동해왔다는 것이 오히려 놀랍습니다.

한국 개발자에게 주는 시사점

PostgreSQL을 사용하는 한국 개발자라면 psql에서 Ctrl+C를 수없이 눌러봤을 텐데, 그 뒤에 이런 메커니즘이 있다는 것을 아는 사람은 많지 않을 겁니다. 이 사례는 몇 가지 중요한 교훈을 줍니다.

첫째, 레거시 프로토콜의 설계 결정은 수십 년간 영향을 미칩니다. PostgreSQL 와이어 프로토콜의 반이중 설계가 30년 후에도 쿼리 취소라는 단순한 기능에 이런 우회적인 구현을 강제하고 있습니다. 프로토콜 설계 시 확장성을 고려하는 것이 얼마나 중요한지 보여주는 사례입니다.

둘째, "작동한다"와 "올바르다"는 다릅니다. 시그널 핸들러에서 네트워크 I/O를 하는 것은 기술적으로 올바르지 않지만, 실용적으로는 작동합니다. 실무에서 이런 트레이드오프를 만나게 될 때, 어디까지가 허용 가능한 해킹이고 어디부터가 시한폭탄인지 판단하는 눈이 필요합니다.

프로덕션 환경에서 장시간 쿼리를 취소해야 한다면, psql의 Ctrl+C보다는 pg_cancel_backend() 함수나 pg_terminate_backend() 함수를 사용하는 것이 더 확실한 방법입니다. 또는 statement_timeout 설정으로 아예 장시간 쿼리를 방지하는 것도 좋은 전략입니다.

마무리

매일 사용하는 도구의 내부를 들여다보면, 예상치 못한 해킹과 역사적 결정의 흔적을 발견하게 됩니다. PostgreSQL의 Ctrl+C 메커니즘은 그런 발견의 좋은 예시이며, 소프트웨어가 어떻게 "완벽하지 않지만 충분히 좋은" 상태로 수십 년간 살아남을 수 있는지를 보여줍니다.

여러분이 사용하는 도구에서 발견한 놀라운 내부 구현이 있다면, 공유해주세요!


🔗 출처: Hacker News

이 뉴스가 유용했나요?

TTJ 코딩클래스 정규반

월급 외 수입,
코딩으로 만들 수 있습니다

17가지 수익 모델을 직접 실습하고, 1,300만원 상당의 자동화 도구와 소스코드를 받아가세요.

144+실전 강의
17개수익 모델
4.9수강생 평점
정규반 자세히 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.