본문으로 건너뛰기
wooncloud

PostgreSQL에서 varchar(n) 대신 text를 권장하는 이유

PostgreSQL에서 varchar(n)은 성능상 이점이 없으며 오히려 불필요한 길이 제한으로 운영 장애를 유발할 수 있습니다. 동일한 저장 구조를 가진 text 타입을 기본으로 사용하고, 데이터 검증이 필요한 경우에만 CHECK 제약을 활용하는 것이 데이터베이스 설계와 유지보수 측면에서 훨씬 효율적입니다

·7 min read· views·

ORM 모델이나 마이그레이션 파일을 보면 문자열 컬럼에 거의 반사적으로 varchar(255)가 붙어 있다. MySQL 시절의 습관이거나 "문자열엔 길이를 줘야 한다"는 막연한 믿음이다. 그런데 PostgreSQL에서는 이 습관이 이득이 거의 없고, 오히려 text가 권장된다. 왜 그런지 내부 저장 방식부터 짚어본다.

PostgreSQL의 문자 타입

  • char(n) / character(n) — 고정 길이. n보다 짧으면 공백으로 패딩한다.
  • varchar(n) / character varying(n) — 최대 n자로 제한한다.
  • varchar — 길이 제한 없는 가변 길이.
  • text — 길이 제한 없는 가변 길이.

여기서 길이 제한이 없는 varchartext는 사실상 동의어다.

셋 다 같은 방식으로 저장된다

핵심은 저장 구조다. PostgreSQL에서 이 타입들은 전부 varlena(variable-length array) 라는 동일한 내부 표현을 쓴다. 즉 textvarchar(10)든 디스크에 들어가는 모양은 같다 — 길이 헤더 + 실제 바이트.

긴 값은 TOAST(The Oversized-Attribute Storage Technique)로 자동 압축되거나 별도 테이블로 분리 저장된다. 이것도 타입과 무관하게 varlena 값이면 똑같이 적용된다.

CREATE TABLE t (a text, b varchar(10), c char(10));
INSERT INTO t VALUES ('hello', 'hello', 'hello');
 
SELECT
  pg_column_size(a) AS text_size,
  pg_column_size(b) AS varchar_size,
  pg_column_size(c) AS char_size
FROM t;
--  text_size | varchar_size | char_size
-- -----------+--------------+-----------
--          6 |            6 |        11

textvarchar(10)는 6바이트로 동일하다(5바이트 + 1바이트 길이 헤더). 반면 char(10)은 'hello'를 10자로 공백 패딩해 11바이트를 쓴다.

성능 차이는 없다

PostgreSQL 공식 문서(8.3 Character Types)가 직접 못 박는다. 요지는 이렇다.

이 세 타입 사이에 성능 차이는 없다. 블랭크 패딩 타입의 저장 공간 증가와, 길이 제한 컬럼에 저장할 때 길이를 검사하는 약간의 CPU 사이클을 빼면. 다른 DB와 달리 PostgreSQL에서는 character(n)이 오히려 추가 저장 비용 때문에 보통 가장 느리다. 대부분의 상황에서는 textcharacter varying를 써야 한다.

다른 DB 경험에서 오는 "varchar(n)이 더 빠르고 작다"는 직관은 PostgreSQL엔 적용되지 않는다. varchar(255)text보다 빠르지도, 작지도 않다. 저장할 때 길이 체크를 한 번 더 할 뿐이다.

그럼 varchar(n)의 비용은?

성능이 같다면 차이는 "제약"에서 온다.

첫째, 임의의 한계가 정상 데이터를 거부한다. varchar(255) 컬럼에 256자가 들어오면 쿼리가 하드 에러로 실패한다.

INSERT INTO users (bio) VALUES (repeat('x', 256));
-- ERROR: value too long for type character varying(255)

255라는 숫자에 도메인적 근거가 있는 경우는 드물다. 대개 그냥 관습이고, 근거 없는 한계가 운영 중 장애로 돌아온다.

둘째, 한계를 바꾸는 건 스키마 마이그레이션이다. PostgreSQL 9.2부터 varchar(n)의 상한을 늘리거나 없애는 건 테이블 재작성 없이 메타데이터만 바꾸면 되는 작업이 됐다.

ALTER TABLE users ALTER COLUMN bio TYPE varchar(1000); -- 늘리기: 빠름(메타데이터)
ALTER TABLE users ALTER COLUMN bio TYPE text;          -- 제한 제거: 빠름

그래도 운영 중인 큰 테이블에 ALTER를 거는 건 락과 배포 절차가 따르는 일이다. 처음부터 text였다면 그 마이그레이션 자체가 필요 없었다. (반대로 상한을 줄이는 건 기존 값을 전부 검증해야 해서 스캔이 필요하다.)

char(n)은 그냥 쓰지 마라

char(n)은 더 나쁘다. 짧은 값을 공백으로 패딩해 저장 공간을 낭비하고, 비교·출력 시 trailing space 처리가 직관과 어긋난다.

SELECT length('ab'::char(5));   -- 2  (저장은 5바이트지만 length는 패딩을 뺀 값)
SELECT 'ab'::char(5) || '|';    -- 'ab|'  (연결 시 패딩 공백이 사라짐 — 직관과 다름)

PostgreSQL 위키도 "Don't use char(n)"이라고 명시한다. 고정 폭 코드(예: 2자리 국가코드)라도 text + CHECK가 더 명확하다.

정말 길이 제한이 필요하면: CHECK 제약

DB 차원의 길이 제한이 도메인상 의미가 있을 때가 있다(폭주 입력 방지, 외부 시스템 규격 등). 그럴 땐 타입에 숫자를 박는 대신 CHECK 제약으로 의도를 드러내는 편이 낫다.

CREATE TABLE users (
  id    bigint PRIMARY KEY,
  email text NOT NULL CHECK (length(email) <= 254),  -- 실무상 이메일 상한
  bio   text
);

CHECK는 (1) 왜 그 한계인지 조건/이름으로 의도를 남기고, (2) 길이 외에 형식 검증(정규식 등)도 함께 걸 수 있으며, (3) 나중에 DROP CONSTRAINT / ADD CONSTRAINT로 타입과 독립적으로 조정된다. 길이 상한과 컬럼 타입을 분리하는 셈이다.

정리

  • PostgreSQL에서 text · varchar · varchar(n)저장 구조가 같고 성능 차이가 없다. char(n)만 패딩으로 손해를 본다.
  • varchar(255) 습관은 다른 DB에서 온 것으로, PostgreSQL에선 이득 없이 임의의 한계만 더한다.
  • 기본값은 text. 진짜 길이 제한이 필요하면 text + CHECK로 의도를 명시하라.

습관적으로 붙이던 (255)를 떼는 것만으로 불필요한 마이그레이션과 "value too long" 장애를 줄일 수 있다.