Tech/mysql | DB모델링 | JPA

TEXT 컬럼은 무조건 피해야할까?

glqdlt 2025. 7. 16. 09:08

예전에 TEXT vs LONG TEXT 에 대해서 다루고(https://glqdlt.tistory.com/504), varchar 로 한글 65355 개를 입력할수 있는지를 다룬 적(https://glqdlt.tistory.com/509)이 있다. 이 때 vs text 에 대한 내용도 살짝 다루었었는데, 오늘은 이를 주제로 다루어볼까 한다.

어떠한 게임 서비스든 웹 서비스든 최초 진입점에 노출되는 공지 사항, 배너 같은 것들이 있을테고, 여기에 사용되는 이미지가 있긴 마련일테다. 이 이미지의 주소를 mysql 테이블에 저장해야한다면, VARCHAR 컬럼을 써야할지 TEXT 컬럼을 써야할지가 고민이 들 수 있다.

이에 대해서는 관습적으로 '어쩔 수 없을 때 TEXT 컬럼을 써라' 라는 대답이 주를 이룬다. 청개구리 심보여서 이를 인정하고 싶진 않지만 사실 맞다. 왠만해서는 VARCHAR 컬럼을 써야 한다. 그러나 단순히 관습적으로 할뿐이지, 어쩔 수 없을 때에 대해서는 많은 사람들이 모르고 있는 것 같더라. 이 어쩔 수 없을 때가 무엇인지를 알아보도록 하자.

InnoDB의 페이지 기반 저장 구조

이전 포스트에서도 알아봤었지만, mysql 의 innoDB 스토리지 엔진에서는 16KB 페이지 단위로 레코드들을 저장한다. 하나의 페이지 안에는 여러 개의 레코드가 저장되는데, 이때 한 레코드의 크기가 클수록 한 페이지에 담을 수 있는 레코드 수는 줄어들게 된다. 레코드의 크기는 인코딩(collation) 과 컬럼의 갯수에 비례해서 계산이 된다. 하나의 페이지에 담을 수 있는 레코드의 갯수가 적어진다면, select 쿼리로 여러 레코드를 질의할 때에 하드디스크 엑세스 빈도가 상대적으로 더 많아지게 된다. 예를 들어 비정규화된 거대한 테이블이 있다면 페이지 안에 담기는 레코드의 갯수는 매우 적을 수 있다.

레코드 크기가 커지면 다음과 같은 문제가 발생할 수 있다:

  • 디스크 I/O가 많아진다.
    • 질의 하는 레코드 갯수가 100개 고정일 때, 테이블 정의, 즉 레코드 크기가 커질 수록 하드디스크 엑세스 빈도가 많아진다.
  • 버퍼 풀, 캐싱 효율이 떨어진다
    • 메모리 리소스도 한도가 있음으로 레코드 사이즈가 커질 수록 캐싱할 수 있는 레코드가 적어진다.
  • 인덱스의 B+Tree 구조가 깊어질 수 있다
    • 레코드 크기와는 무관하지만, 레코드 크기를 키우는 큰 컬럼을 인덱스로 잡을 경우에 인덱스도 페이지 단위로 관리 됨으로 인덱스 최적화가 어려워진다.
  • 랜덤 액세스 성능이 저하된다
    • 위의 인덱스의 이슈와 맥락이 같다. 인덱스 구조가 깊어질수록 랜덤 액세스 빈도가 늘어난다. 트리가 깊어질 수록 인덱스 페이지 갯수도 늘어나기 때문.

긴 VARCHAR가 성능에 미치는 영향

VARCHAR(N) 타입은 내부적으로 실제 저장되는 데이터 길이에 따라 공간을 차지하지만, N이 커질수록 레코드의 최대 길이도 늘어난다. 예를 들어 VARCHAR(2000) 컬럼은 평균적으로 상당히 큰 레코드 크기를 유발하게 된다.

이로 인해 다음과 같은 현상이 발생한다:

항목 영향
페이지당 row 수 감소 전체 테이블의 물리적인 크기 증가
랜덤 access 비용 증가 디스크 seek 횟수 증가
버퍼풀 효율 감소 같은 메모리 공간에 캐시되는 row 수 감소
테이블 스캔 느려짐 같은 양의 데이터를 더 많은 페이지로 나누어 접근

결국, VARCHAR의 최대 길이를 필요 이상으로 크게 잡는 것은 디스크 공간과 성능 모두에 악영향을 줄 수 있다.

TEXT 타입과의 트레이드오프

그렇다면 TEXT 타입은 어떨까? InnoDB에서 TEXT는 일반적으로 레코드 안에는 포인터(20 bytes)만 저장하고, 본문 데이터는 외부 페이지(external page)에 저장된다.

TEXT의 장점은 레코드 자체 크기는 작아져서 페이지에 포함되는 레코드 갯수가 많아질 수 있다. 테이블이 전체적으로 가벼워진다는 것이다. 그러나 단점 역시 명확하게 있는데, 레코드에는 TEXT 컬럼의 포인터만 있을 뿐이기에 실제 값-본문을 읽을 때에는 추가 하드디스크 I/O 가 발생한다. 또한 읽어들이 TEXT 컬럼의 값은 버퍼풀에 캐싱되지 않고 쿼리 결과 송출 버퍼(result buffer) 에서 휘발성으로 담기기 때문에, 동일한 레코드를 읽은 여러 세션들이 재활용(캐싱)하지 못하고 소비되는 식으로 처리 됨으로 효율적이지 못하다.

그래서 경우에 따라서는 TEXT 컬럼을 강제로 정규화 하기도 한다. 필요할때만 TEXT 컬럼이 있는 테이블을 JOIN 해서 읽으라는 식인 거다. 왜냐면 지금까지 알아본것처럼 TEXT 컬럼이 SELECT 쿼리 반환 대상이면 external page 를 경유하는 오버헤드가 일어나기 때문에 이를 피하고자 함이다.

정리

상황 권장 타입
값의 길이가 짧고 WHERE 검색/정렬에 자주 쓰임 VARCHAR
값이 매우 길고 (수백~수천 바이트), 자주 조회되지 않음 TEXT
row density를 중요시해야 함 (대량 데이터, 랜덤 access 많음) TEXT 고려
성능이 중요한 WHERE 절에서 문자열 비교가 있음 VARCHAR 필수

InnoDB에서는 레코드의 크기, 페이지의 크기, 외부 저장 여부 등 여러 요인이 성능에 영향을 준다. 특히 긴 문자열을 저장할 때는 단순히 타입의 차이뿐 아니라, 데이터 접근 패턴, 인덱스 사용 여부, 읽기 빈도 등을 모두 고려해야 한다.

 

레퍼런스

- 당근 마켓 기술 블로그 https://medium.com/daangn/varchar-vs-text-230a718a22a1