본문으로 건너뛰기
wooncloud

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

PostgreSQL 에서 varchar(n) 과 text 는 저장 구조(varlena)가 같아 성능 차이가 없고, varchar(n) 은 임의의 길이 제한으로 운영 장애만 유발하므로 기본값은 text + CHECK 를 권장합니다.

·8 min read·— views··

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 |        11

text와 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" 장애를 줄일 수 있습니다.