PostgreSQL에서 varchar(n) 대신 text를 권장하는 이유
PostgreSQL 에서 varchar(n) 과 text 는 저장 구조(varlena)가 같아 성능 차이가 없고, varchar(n) 은 임의의 길이 제한으로 운영 장애만 유발하므로 기본값은 text + CHECK 를 권장합니다.
ORM 모델이나 마이그레이션 파일을 보면 문자열 컬럼에 거의 반사적으로 varchar(255)가 붙어 있습니다. MySQL 시절의 습관이거나 "문자열엔 길이를 줘야 한다"는 막연한 믿음입니다. 그런데 PostgreSQL에서는 이 습관이 이득이 거의 없고, 오히려 text가 권장됩니다. 왜 그런지 내부 저장 방식부터 짚어봅니다.
PostgreSQL의 문자 타입
char(n)/character(n)— 고정 길이입니다. n보다 짧으면 공백으로 패딩합니다.varchar(n)/character varying(n)— 최대 n자로 제한합니다.varchar— 길이 제한 없는 가변 길이입니다.text— 길이 제한 없는 가변 길이입니다.
여기서 길이 제한이 없는 varchar와 text는 사실상 동의어입니다.
셋 다 같은 방식으로 저장된다
핵심은 저장 구조입니다. PostgreSQL에서 이 타입들은 전부 varlena(variable-length array) 라는 동일한 내부 표현을 씁니다. 즉 text든 varchar(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 | 11text와 varchar(10)는 6바이트로 동일합니다(5바이트 + 1바이트 길이 헤더). 반면 char(10)은 'hello'를 10자로 공백 패딩해 11바이트를 씁니다.
성능 차이는 없다
PostgreSQL 공식 문서(8.3 Character Types)가 직접 못 박습니다. 요지는 이렇습니다.
이 세 타입 사이에 성능 차이는 없습니다. 블랭크 패딩 타입의 저장 공간 증가와, 길이 제한 컬럼에 저장할 때 길이를 검사하는 약간의 CPU 사이클을 빼면. 다른 DB와 달리 PostgreSQL에서는
character(n)이 오히려 추가 저장 비용 때문에 보통 가장 느립니다. 대부분의 상황에서는text나character 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" 장애를 줄일 수 있습니다.