프로젝트를 리팩토링하면서 @Lob
을 사용한 필드가 MySQL에서 tinytext를 default로 맵핑하는 것을 볼 수 있었다(@Lob만 사용했을 때). tinytext는 0~255바이트만큼 데이터를 할당할 수 있는데 새로운 요구사항이 500자 이내의 한글을 입력할 수 있어야 하기에 utf8mb4기준, 최대 2000바이트 입력이 가능하도록 리팩토링하면서 관련 공부한 내용과 함께 정리하려고 한다.
먼저 기존에 왜 @Lob
을 사용하여 LOB컬럼으로 맵핑되도록 했는지 회고해보자. 예전엔 단순히 @Lob
을 상대적으로 다른 컬럼보다 많은 공간이 필요하다고 생각하는 데이터를 저장하기 위해서 사용했다. 왜냐하면 MySQL에서 하나의 레코드에 저장할 수 있는 최대 길이는 65,535 Byte로 정해져있고, LOB 데이터는 Off-Page라는 외부 공간에 저장되어 이 제한 사항에 거의 영향을 끼치지 않는다고 알고 있었기 때문이다.
tinytext는 255바이트까지 데이터를 할당할 수 있다. 그럼 255바이트 만큼 외부 공간에 데이터를 저장하는 것일까? 그렇지 않다.
공식 문서를 보면
이와 같이 256바이트가 넘지 않는 TEXT와 BLOB 데이터는 외부 공간에 저장되는 것이 아닌 inline 테이블에 저장된다는 것을 확인할 수 있다.
그렇다면 tinytext와 varchar(255)의 차이가 뭘까??
먼저 tinytext는 0~255바이트만큼 데이터를 할당할 수 있다고 했는데 그렇다면 varchar(255)와는 무슨 차이가 있을까?
가장 대표적인 차이는 데이터 할당 크기가 가변적이냐, 불변적이냐의 차이이다. 또한, text와 같은 LOB컬럼은 미리 할당되지 않아 매번 레코드를 읽고 쓸 때마다 필요한 만큼 메모리를 할당해야 하지만 varchar는 내부적으로 MySQL서버 내부에 캐싱되어 여러 커넥션에서 공유하여 사용될 수 있다는 성능 상 이점도 있다(조회 시 temp table을 메모리에 생성하지 않고 디스크에 생성).
이외에도 text, blob은 defalult 값을 지정할 수 없다는 점 또한 있다.
그럼 varchar를 사용해야지, tinytext를 사용할 필요가 있을까?
나는 위 특징들 때문에 딱히 필요없다고 생각한다. 그래도 왜 tinytext가 있는지 궁금해서 찾아봤는데 여러 가설 중, TEXT와 BLOB의 일관성을 위해 존재한다는 가설이 가장 맞는 것 같다 생각했다.
BLOB타입의 종류로 TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB이 있는데
TEXT타입 또한, TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT로 BLOB과 상응하게 매칭되도록 한 것이다.
돌고 돌아 다시 리팩토링 이슈로 돌아와서 두 가지 방법을 생각해볼 수 있었다.
@Lob
@Column(columnDefinition = "TEXT")
첫 번째 방법은 TEXT컬럼으로 맵핑되도록 해서 기존 255바이트만 쓸 수 있던 tinytext 대신 text로 맵핑되도록 하여 65535바이트까지 쓸 수 있도록 하는 것이다.
@Column(length = 500)
두 번째 방법은 VARCHAR컬럼으로 맵핑되도록 하는 것이다. 컬럼 어노테이션과 length 옵션만 추가로 주어 varchar(500)으로 맵핑되도록 한다. 현재 연결되어 있는 character_set이 utf8mb4이기에 최대 500 * 4Bytes = 2000Bytes의 데이터가 해당 컬럼에 들어올 수 있다.
첫 번째 방법은 자원의 오용 가능성이 있다고 생각했기에 두 번째 방법을 사용해 varchar로 맵핑시키기로 했다.
현재 image_url과 contents가 @Lob
을 사용하여 tinytext인 것을 확인할 수 있다.
이제 이 테이블 레코드들의 최대 바이트 길이를 살펴보자.
created_at, updated_at은 datetime(6)이다.
datetime은 위 표와 같이 5.6.4버전 이후로 5bytes + fractional seconds storage이다.
fractional seconds는 소수부의 길이를 의미하는 것으로 datetime(6)이므로 소수부의 길이를 6까지 지원한다.
그리고 이는 3bytes가 추가로 필요하다.
따라서 datetime(6)은 5 bytes + fractional seconds storage = 5 + 3 = 8 bytes가 필요하다.
id는 bigint로 8bytes, 나머지 컬럼들은 모두 varchar로 (선언된 컬럼 길이 + 1)의 바이트 수가 필요하다.(255바이트 넘어가면 +2바이트 필요)
이렇게 계산해보면 두 tinytext컬럼을 제외하고, 총 24 + 10 + (15 + 7 + 10 + 15 + 50 2 + 255 4) * 4 = 4702bytes가 필요하다.
두 컬럼에 필요한 데이터 크기가 약 60000bytes가 아닌 이상, 레코드 최대 제한 길이 65,535bytes에는 절대 닿지 않는다. 즉, 현재 테이블에서 LOB데이터를 쓸 이유가 전혀 없이 varchar타입을 사용하면 되는 것이다.
따라서 위처럼 @Column(length=?)옵션을 사용해 varchar타입으로 맵핑되도록 리팩토링했다.
LOB 데이터를 무분별하게 사용했던 것을 반성하면서 각 데이터 타입에 따라 몇 바이트가 필요하고, 어떻게 저장하는지 공부할 수 있었다. 혹여나 나와 같이 @Lob 어노테이션을 무분별하게 사용하던 사람이 있다면 도움이 되었으면 좋겠다.