---
title: "PostgreSQL에서 varchar(n) 대신 text를 권장하는 이유"
description: "PostgreSQL에서 varchar(n)은 성능상 이점이 없으며 오히려 불필요한 길이 제한으로 운영 장애를 유발할 수 있습니다. 동일한 저장 구조를 가진 text 타입을 기본으로 사용하고, 데이터 검증이 필요한 경우에만 CHECK 제약을 활용하는 것이 데이터베이스 설계와 유지보수 측면에서 훨씬 효율적입니다"
date: 2026-06-11
updated: 2026-06-11T01:49:44.658Z
tags: [postgresql, database, sql, 데이터모델링]
canonical: https://blog.wooncloud.com/posts/postgresql-text-vs-varchar
---

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 값이면 똑같이 적용된다.

```sql
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자가 들어오면 쿼리가 하드 에러로 실패한다.

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

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

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

```sql
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 처리가 직관과 어긋난다.

```sql
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 제약**으로 의도를 드러내는 편이 낫다.

```sql
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" 장애를 줄일 수 있다.
