포스트

DB 버전이 다르면 같은 쿼리가 깨진다 — PostgreSQL 버전 호환성

PostgreSQL 메이저 버전 차이로 같은 쿼리가 깨지는 이유와 대응. 암묵 형변환 강화, 집계·정렬 동작 변화, 예약어 추가를 진단하고 호환되게 고친다.

DB 버전이 다르면 같은 쿼리가 깨진다 — PostgreSQL 버전 호환성

그 주엔 같은 쿼리가 한 환경에서는 멀쩡히 돌고 다른 환경에서는 형변환 오류를 뱉는 상황을 다뤘다. 코드도 데이터도 같았다. 다른 것은 단 하나, DB 엔진의 메이저 버전이었다. 라이브러리 업그레이드와 달리 DB 버전 차이는 애플리케이션 코드에 흔적을 남기지 않아 진단이 더 까다롭다. 핵심은 버전을 가정하지 않는 쿼리를 쓰는 것이다.

왜 같은 SQL이 버전에 따라 다르게 동작하는가

SQL은 표준이 있지만 각 DB는 방언(dialect)을 갖고, 그 방언마저 메이저 버전마다 바뀐다. PostgreSQL이 버전을 올리며 깨뜨리는 대표적 변화는 세 갈래다.

첫째, 암묵 형변환 규칙의 강화. 과거 버전은 일부 타입 간 자동 캐스트를 너그럽게 허용했다. 예컨대 문자열과 숫자, 정수와 불리언 사이의 묵시적 변환이 일부 연산자에서 통했다. 이후 버전은 타입 안정성을 위해 이런 묵시적 캐스트를 제거했다. 그러면 예전엔 통하던 비교가 operator does not exist: text = integer 같은 오류로 바뀐다.

1
2
3
4
5
6
7
-- 예전 버전에선 통하기도 했던 위험한 비교
SELECT * FROM orders WHERE status = 1;   -- status가 varchar인데 정수와 비교

-- 신버전: 묵시적 캐스트 없음 → 오류
-- 호환되게: 양쪽 타입을 명시적으로 맞춘다
SELECT * FROM orders WHERE status = '1';
SELECT * FROM orders WHERE status = CAST(1 AS varchar);

둘째, 집계·정렬·null 처리의 기본 동작 변화. 함수의 반환 타입 정밀도, NULLS FIRST/LAST의 기본값, 문자열 정렬에 쓰이는 콜레이션(collation) 기본값이 버전·OS 라이브러리에 따라 달라진다. 같은 ORDER BY name이 한쪽에서는 사전순, 다른 쪽에서는 바이트순으로 정렬돼 결과 순서가 미묘하게 바뀐다.

셋째, 예약어 추가. 신버전이 새 키워드를 예약어로 등록하면, 그 단어를 따옴표 없이 컬럼/별칭으로 쓰던 쿼리가 문법 오류로 깨진다.

호환되게 쓰는 원칙

원칙은 단순하다. 암묵에 기대지 말고 명시한다.

1
2
3
4
5
6
7
8
9
10
11
12
-- 1) 타입은 항상 명시적으로 캐스트
WHERE created_at >= CAST('2025-01-01' AS timestamp)
WHERE total::numeric(12,2) > 0

-- 2) null 정렬 순서를 가정하지 않고 명시
ORDER BY priority DESC NULLS LAST

-- 3) 콜레이션이 결과에 영향을 준다면 명시
ORDER BY name COLLATE "C"        -- 바이트순으로 고정

-- 4) 예약어가 될 수 있는 식별자는 항상 따옴표/별칭 회피
SELECT u.user AS user_name FROM users u   -- "user"는 예약어

마이그레이션 시 진단·검증 절차

버전 올리기 전에 깨질 곳을 미리 찾는다.

1
2
3
4
5
6
7
-- 묵시적 캐스트에 의존하는 표현식 후보를 점검
-- (테스트 DB에서 실제 실행해 오류 나는 쿼리를 수집한다)
EXPLAIN SELECT ...;   -- 플랜이 같은지, 형변환이 끼는지 확인

-- 함수/연산자 시그니처가 바뀌었는지 카탈로그로 확인
SELECT proname, prorettype::regtype
FROM pg_proc WHERE proname = 'date_trunc';

실무 절차는 이렇다. (1) 신버전 인스턴스를 동일 데이터로 띄운다. (2) 애플리케이션의 쿼리 로그를 모아 신버전에서 일괄 실행해 오류·결과 차이를 수집한다. (3) 오류는 명시 캐스트로, 순서 차이는 명시 정렬/콜레이션으로 고친다. (4) 회귀 테스트에 “버전 간 결과 동일성”을 검증하는 케이스를 추가한다.

운영 함정

첫째, “테스트는 통과했는데 운영에서 깨짐.” 개발 DB와 운영 DB의 마이너 버전이나 콜레이션 라이브러리가 다르면, 같은 메이저 버전이라도 정렬 결과가 갈린다. 개발·운영의 DB 버전과 로케일을 일치시키는 것이 1순위다.

둘째, 클라이언트 드라이버 호환성. 엔진만 올리고 JDBC 드라이버를 그대로 두면, 신버전이 도입한 프로토콜·타입을 드라이버가 모른다. 엔진과 드라이버 버전을 함께 올려야 한다.

핵심 요약

  • DB 메이저 버전 차이는 코드에 흔적을 남기지 않아 진단이 어렵다. 주범은 암묵 형변환 제거, 정렬·콜레이션 기본값 변화, 예약어 추가다.
  • 대응 원칙은 “명시”다. 타입은 명시 캐스트, null 정렬·콜레이션은 명시 지정, 예약어가 될 식별자는 피한다.
  • 업그레이드 전 동일 데이터로 신버전을 띄워 쿼리 로그를 일괄 실행하고, 개발·운영의 버전·로케일·드라이버를 일치시킨다.

면접 한 줄 Q&A — “DB 버전 올렸더니 operator does not exist: text = integer가 났다. 왜?” → “신버전이 묵시적 형변환을 제거했기 때문이다. 비교 양쪽 타입을 명시적으로 캐스트해 맞춘다.”

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.