문제 상황 - URL 길이 초과로 인한 에러 발생

ajouevent.com 서비스에서 학교 공지사항을 크롤링하던 중, 데이터베이스에 이미지 URL을 저장하는 과정에서 다음과 같은 에러가 발생했다.
2025/01/03 16:11:24 utils.go:156: {"timestamp":"2025-01-03T07:11:24.327+00:00","message":"could not execute statement [Data truncation: Data too long for column 'url' at row 1] [insert into club_event_image (club_event_id,url) values (?,?)]; SQL [insert into club_event_image (club_event_id,url) values (?,?)]","details":"uri=/api/webhook/crawling"}
크롤링 중 에러가 발생한 이미지 URL은 다음과 같다.
원인 분석

이미지 URL의 길이가 총 276자로 VARCHAR(255)의 최대 길이를 초과했기 때문에 “Data truncation: Data too long for column ‘url’ at row1” 에러가 발생한 것이다.
해결 방안
따라서 URL 저장 시 데이터 타입과 길이를 재설정해야 했다.
이를 위해 URL 저장 방식, 데이터 타입, 그리고 URL의 길이 제한을 다시 고려했다.
URL 저장 방식
공지사항 크롤링을 통해 가져온 이미지 URL
- 학교 공지사항 글에 포함된 이미지는 이미 학교 서비스를 통해 URL 형태로 생성된 상태다.
- ex) https://www.ajou.ac.kr/_attach/ajou/editor-image/2024/12/lwDjcIfEGlYfXOdCCNwsPtkhVK.png
- *학교 공지사항 글을 크롤링 하고, 이미지를 따로 AWS S3, Google Cloud Storage, Firbase Storage 같은 스토리지 서비스를 통해 저장하지는 않았다. 학교 공지사항 글에 포함된 이미지는 이미 학교 서비스를 통해 URL 형태로 생성된 상태다.

ajouevent.com에서 게시글을 작성하면서 첨부하는 이미지
- 사용자가 ajouevent.com에서 게시글을 작성할 때, 이미지는 Presigned URL을 통해 AWS S3에 업로드된다.
- 업로드된 이미지는 S3의 객체 URL을 통해 조회가 가능하다.
DB에 이미지 URL 저장 vs 이미지 데이터 직접 저장
이미지 데이터를 직접 저장하려면 BLOB, LONGBLOB과 같은 데이터 타입을 사용해야 하지만, 이는 아래와 같은 문제를 유발한다.
- 데이터베이스 크기 증가: 이미지 파일이 데이터베이스의 크기를 증가시킴
- 데이터베이스 성능 저하: 이미지를 바이너리 데이터로 변환하여 데이터베이스에 저장하고 읽는, 이미지 읽기와 쓰기 과정에서 느려질 가능성이 높다.
→ 이미 학교 서비스를 통해 만들어진 URL, S3를 통해 만들어진 이미지 URL을 바로 MySQL DB의 column으로 저장하는 방식을 택했다.
데이터 타입 결정: CHAR vs VARCHAR vs TEXT
URL을 DB column으로 저장할 때 데이터 타입을 어떻게 할지 고민이 있었다.
CHAR vs VARCHAR
- CHAR: 고정 길이 데이터(고정형) 저장에 적합.
- VARCHAR: 가변 길이 데이터(가변형) 저장에 적합하며, 길이에 따라 메모리 공간을 효율적으로 사용.
→ URL의 길이는 고정되지 않는 가변 길이이므로, VARCHAR가 더 적합하다.
VARCHAR vs TEXT
VARCHAR와 TEXT의 차이에 대해서는 Real MySQL 8.0의 저자인 이성욱 당근 DBA께서 잘 설명해놓으신 글이 있다.
https://medium.com/daangn/varchar-vs-text-230a718a22a1
VARCHAR vs TEXT
개요
medium.com
요약하자면
- MySQL 서버는 레코드의 컬럼 데이터를 B-Tree(Clustering Index)에 Inline으로 저장한다. 만약 레코드의 전체 크기가 InnoDB 스토리지 엔진의 최대 레코드 크기 제한(16KB 페이지에서는 8,117 바이트)을 초과하면, 길이가 긴 컬럼을 선택해서 off-Page로 저장하게 된다. (*이때, VARCHAR, TEXT 두 타입의 컬럼 모두 컬럼에 저장된 값으로 인해 최대 레코드 크기 제한을 넘어서면 off-page로 저장된다)
- → VARCHAR 컬럼에 너무 큰 길이를 사용하면, off-Page에 저장될 수 있고, 이는 성능 이슈와 직결된다.
- VARCHAR 타입은 최대 크기가 설정되기 때문에, 메모리 공간을 records[2] 버퍼에 최대 크기로 미리 할당해두어 메모리를 활용한다. records[2] 메모리 버퍼에 할당되면 많은 커넥션에 의해 재사용된다. 반면에 TEXT는 실제 최대 크기만큼 메모리를 할당해두면 메모리 낭비가 너무 심해진다.
- 대신, VARCHAR 타입에 저장된 값이 길어서 off-Page로 저장된 경우 MySQL 서버는 records[2] 버퍼를 사용하지 못하고 새롭게 메모리 공간을 할당해서 사용한다. 그래서 VARCHAR 타입에 매우 큰 값이 빈번하게 저장되는 경우는 주의가 필요하다.
→ 따라서 VARCHAR에 저장되는 URL의 최대 길이를 정하고, VARCHAR 컬럼이 추가되면서 레코드의 크기가 InnoDB 스토리지 엔진의 최대 레코드 크기 제한을 넘는지 비교해야했다.
URL 길이 제한
MySQL DB의 column으로 저장되는 URL의 길이가 어느정도인지에 따라 VARCHAR를 쓸지 TEXT를 쓸지 정해진다고 판단했다.
일반적인 브라우저 지원 길이



- 크롬, 인터넷 익스플로러, 엣지의 경우는 2,083자 Firefox는 65,536자, Safari는 80,000자까지 허용한다.
Presigned URL 및 CDN URL
Presigned URL
- AWS S3 Presigned URL, Google Cloud Storage와 같은 스토리지에서 생성되는 URL은 보안 토큰, 만료 시간, 키 경로 등을 포함하여 길이가 길어질 수 있다.
- 따라서 복잡한 쿼리 파라미터가 추가되면 더 늘어날 가능성은 있다.
기존 DB에 저장된 이미지 URL과 비교

기존에 DB에 저장된 2016년부터의 아주대학교 공지사항의 20만 개 이미지 URL을 분석했을 때, 2025년 1월 18일 기준 가장 긴 URL의 길이는 291자다.
이미지 생성 패턴
- 아주대 도메인(ajou.ac.kr)
- 이미지가 아주대 도메인(ajou.ac.kr)의 다양한 하위 도메인에서 업로드 됨.
- 아주대 공식 홈페이지 - https://www.ajou.ac.kr/_attach/ajou/editor-image/2024/12/lwDjcIfEGlYfXOdCCNwsPtkhVK.png
- 아주대 소프트웨어학과 홈페이지 - http://software.ajou.ac.kr/smart_editor/upload/20241211161530891547026.jpg
- 아주대 일자리센터 홈페이지 -https://job.ajou.ac.kr/upload/Recruit/20250107_40001543/250107%20[%EC%A0%95%EA%B7%9C][%EC%97%A0%EB%84%A5%EC%8A%A4]%20%EC%83%9D%EC%82%B0%EA%B4%80%EB%A6%AC,%EC%97%B0%EA%B5%AC%EA%B8%B0%ED%9A%8D%ED%8C%80%20%EC%B6%94%EC%B2%9C%EC%B1%84%EC%9A%A9.jpg
- URL 구조:
- 저장 디렉토리
- 대부분 /_attach/editor-image/나 /_resources/_upload_image/ 디렉토리 하위에 저장됨. 공지사항 작성 시스템에서 자동으로 지정된 경로로 업로드.
- 파일 이름 규칙
- 랜덤 ID 방식: 난수 조합으로 파일 이름이 자동 생성되어 중복 방지 (lwDjcIfEGlYfXOdCCNwsPtkhVK.png).
- 제목 포함 방식: 제목이나 작성자 정보를 인코딩 (250107 [%EC%A0%95%EA%B7%9C] [%EC%97%A0%EB%84%A5%EC%8A%A4] 생...jpg).
- 저장 디렉토리
- 이미지가 아주대 도메인(ajou.ac.kr)의 다양한 하위 도메인에서 업로드 됨.
- Google Drive 링크
- 공지사항에 Google Drive에서 공유된 이미지를 포함.
- 링크에 export=view&id=<파일ID>와 같은 매개변수가 포함됨.
- 외부 클라우드 기반 스토리지를 이용하는 방식으로, 학교 서버에 이미지를 저장하지 않는 경우 사용.
- 외부 도메인 (sktelecom.com)
- 외부 협력업체나 제휴 프로그램(예: SK텔레콤)과 관련된 이미지.
- 외부 도메인의 리소스를 직접 참조.
- URL은 외부 시스템에서 관리됨.
그래서 DB column에 저장되는 URL 길이 제한은 얼마로?
따라서 URL의 길이 제한은
- 지금까지 저장된 아주대학교 공지사항의 이미지 URL 길이가 300자를 넘지 않았다.
- 외부 URL을 가져오는 경우는 드물며, 가져오더라도 길이가 짧은 편이다.
- URL에 쿼리 매개변수가 과도하게 포함되거나, 이미지 데이터를 base64로 인코딩하거나, AWS S3, 구글 드라이브처럼 외부 서비스에서 생성된 링크가 길고 복잡하더라도 브라우저에서 지원하는 URL 길이 제한을 초과하는 경우는 거의 없다는 점을 고려하여
→ 점유율이 가장 높은 크롬 브라우저의 URL 길이 제한과 동일하게 2083자로 설정했다.
*그래도 추가적으로 백엔드 로직에서 URL 길이가 2,083자를 넘으면 로그를 기록하거나 에러 메시지를 반환하도록 처리했다.
결론 - VARCHAR(2083) 선택
결론적으로 VARCHAR(2083)를 선택한 이유는 아래와 같다.
- 이미지 url을 사용하는 이미지 테이블의 레코드가 image_id(bigint), url(VARCHAR(2083), post_id(bigint)로 설정하면 기타 오버헤드를 포함해도 2100바이트 정도로, 현재 InnoDB 스토리지 엔진의 최대 레코드 크기 제한(16KB 페이지에서는 8,117 바이트)을 넘어서지 않는다
- club_event_image 테이블에 추가적인 컬럼이 들어갈 가능성이 낮다
- 추가되더라도 VARCHAR(2083) 같은 긴 컬럼이 3개~4개 추가될 가능성이 적기 때문에, 컬럼이 추가되면서 레코드 용량을 넘어설 가능성이 적다.
CREATE TABLE club_event_image (
image_id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 8바이트
club_event_id BIGINT NOT NULL, -- 8바이트
url VARCHAR(2083) NOT NULL -- 최대 2083바이트 + 2바이트(길이 오버헤드)
);
image_id (8) + club_event_id (8) + url(2085) + 기타 오버헤드 <= 8117바이트


- URL의 길이는 일반적으로 2,083자를 넘지 않는다. → VARCHAR(2083)을 사용하면 “inline” 방식으로 데이터를 저장하여 효율적인 처리가 가능
- 매우 긴 URL이라고 하더라도 브라우저에서 지원하는 URL을 넘길 가능성이 적다.
- 게시글을 조회할 때마다 이미지 테이블을 조회하고, 항상 URL(이미지) 컬럼의 데이터가 필요하다. (자주 접근) → TEXT보다 VARCHAR가 효율적
글을 작성하며 알게 된 사실
- VARCHAR라고 무조건 Inline으로 저장하고, TEXT라고 무조건 off-Page 저장 X
- InnoDB 스토리지 엔진은 레코드의 전체 크기가 제한을 초과하면 길이가 긴 컬럼을 선택해서 Off-Page로 저장하게 된다 → VARCHAR 컬럼에 저장된 값이 큰 경우에도 off-Page로 저장된다
- VARCHAR를 사용한다고 하더라도 off-Page로 넘어가면(off-page 저장소로 분리 저장되는 순간) 메모리 버퍼를 사용할 수 없기 때문에 조심해야 한다. (매번 매모리를 새로 할당 및 해제)
- VARCHAR를 쓰면 실제 저장되는 데이터 크기 만큼 메모리 공간을 차지함
- ex) VARCHAR(2083) 컬럼에 “Hello”라는 5글자를 저장하면: 저장 공간 = 5(데이터) + 1(오버헤드) = 6바이트만 사용.
- 다만 TABLE 구조체의 records[2] 버퍼를 이용하는 과정에서 실제 레코드의 데이터 크기에 관계 없이 최대 크기로 메모리 할당을 해두기 때문에 VARCHAR(255)와 VARCHAR(2083)의 성능 차이가 발생한다.
- VARCHAR는 index를 생성할 수 있고, TEXT는 index를 생성할 수 없다. X
- 둘 다 최대 크기 길이 제한만 충족 시키면 인덱스 생성 가능
- B-Tree 인덱스뿐만 아니라 전문 검색 인덱스도 TEXT 타입과 VARCHAR 타입 컬럼 모두 동일하게 생성할 수 있음
-- // 컬럼 그대로 사용시, 인덱스 생성 불가
mysql> ALTER TABLE tb_varchar ADD INDEX ix_fd (fd);
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes
mysql> ALTER TABLE tb_lob ADD INDEX ix_fd (fd);
ERROR 1170 (42000): BLOB/TEXT column 'fd' used in key specification without a key length
-- // 컬럼 값의 길이(프리픽스)를 지정하면, 인덱스 생성 가능
mysql> ALTER TABLE tb_varchar ADD INDEX ix_fd ( fd(50) );
mysql> ALTER TABLE tb_lob ADD INDEX ix_fd ( fd(50) );
참고
https://medium.com/daangn/varchar-vs-text-230a718a22a1
VARCHAR vs TEXT
개요
medium.com
'ajouevent.com' 카테고리의 다른 글
| 유튜브처럼, 구독한 공지사항별 알림 설정 기능을 도입해보자 (0) | 2025.10.02 |
|---|---|
| FCM 키워드 알림 기능 개발기 feat. FCM Topic 한글, 특수문자 사용 가능 여부 (3) | 2025.01.12 |
| ajouevent.com 소개 - 아주대 공지사항 및 이벤트 알림 웹앱(PWA, FCM) (3) | 2025.01.05 |