PostgreSQL에서 byte 초과 방지 트리거 생성하기 : byte 초과 시 잘라주는 트리거
PostgreSQL에서 문자열을 BYTE 기준으로 제한하려면 OCTET_LENGTH()를 이용한 BEFORE INSERT/UPDATE 트리거가 정석입니다.
1. 트리거 함수 생성
CREATE OR REPLACE FUNCTION fn_check_byte_length()
RETURNS TRIGGER AS $$
BEGIN
-- 예: content 컬럼이 2000byte 초과하면 에러
IF NEW.content IS NOT NULL AND OCTET_LENGTH(NEW.content) > 2000 THEN
RAISE EXCEPTION 'content 컬럼은 2000byte를 초과할 수 없습니다. 현재: %byte',
OCTET_LENGTH(NEW.content);
END IF; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
2. 트리거 생성
CREATE TRIGGER trg_check_byte_length
BEFORE INSERT OR UPDATE
ON 테이블명
FOR EACH ROW
EXECUTE FUNCTION fn_check_byte_length();
3. 여러 컬럼 동시에 체크 (실무형)
CREATE OR REPLACE FUNCTION fn_check_byte_length_multi()
RETURNS TRIGGER AS $$
BEGIN
-- content
IF NEW.content IS NOT NULL AND OCTET_LENGTH(NEW.content) > 2000 THEN
RAISE EXCEPTION 'content 2000byte 초과 (% byte)', OCTET_LENGTH(NEW.content);
END IF; -- title (예: 100byte 제한)
IF NEW.title IS NOT NULL AND OCTET_LENGTH(NEW.title) > 100 THEN
RAISE EXCEPTION 'title 100byte 초과 (% byte)', OCTET_LENGTH(NEW.title);
END IF; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
4. 공통화 (재사용 가능한 함수)
👉 컬럼명 + 제한값 파라미터로 받기
CREATE OR REPLACE FUNCTION fn_check_byte_length_dynamic()
RETURNS TRIGGER AS $$
DECLARE
col_name TEXT := TG_ARGV[0];
max_len INT := TG_ARGV[1]::INT;
val TEXT;
BEGIN
-- 동적 컬럼 값 추출
EXECUTE format('SELECT ($1).%I', col_name) INTO val USING NEW; IF val IS NOT NULL AND OCTET_LENGTH(val) > max_len THEN
RAISE EXCEPTION '% 컬럼은 % byte 초과 불가 (현재 % byte)',
col_name, max_len, OCTET_LENGTH(val);
END IF; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
트리거 적용
CREATE TRIGGER trg_check_content
BEFORE INSERT OR UPDATE ON 테이블명
FOR EACH ROW
EXECUTE FUNCTION fn_check_byte_length_dynamic('content', '2000');
실무 팁
- DB 레벨에서 막으면 무조건 데이터 무결성 보장됨
- Java에서도 미리 체크 + DB 트리거 이중 방어 추천
- 에러 메시지에 byte 포함시키면 디버깅 쉬움
- 성능 영향 거의 없음 (단순 LENGTH 체크)
주의
- VARCHAR(n)은 문자 기준이라 byte 제한 못함
- 반드시
OCTET_LENGTH()사용해야 함
자동으로 byte 초과 시 잘라주는 트리거 (truncate)
이번엔 에러가 아니라 → 자동으로 byte 기준으로 잘라주는 트리거 만들어줄게.
(실무에서 메시지/LMS 처리할 때 많이 씀)
핵심 포인트
LEFT()❌ → 문자 기준- byte 기준으로 자르려면 루프 필요 (UTF-8 대응)
1. byte 기준 truncate 함수 (핵심 🔥)
CREATE OR REPLACE FUNCTION fn_truncate_by_byte(
input_text TEXT,
max_bytes INT
)
RETURNS TEXT AS $$
DECLARE
result TEXT := '';
i INT := 1;
len INT := CHAR_LENGTH(input_text);
current_char TEXT;
BEGIN
IF input_text IS NULL THEN
RETURN NULL;
END IF; WHILE i <= len LOOP
current_char := SUBSTRING(input_text FROM i FOR 1); -- 추가했을 때 byte 초과하면 중단
IF OCTET_LENGTH(result || current_char) > max_bytes THEN
EXIT;
END IF; result := result || current_char;
i := i + 1;
END LOOP; RETURN result;
END;
$$ LANGUAGE plpgsql;
2. 트리거 함수 (자동 truncate)
CREATE OR REPLACE FUNCTION fn_truncate_trigger()
RETURNS TRIGGER AS $$
BEGIN
-- content 2000 byte 제한
IF NEW.content IS NOT NULL THEN
NEW.content := fn_truncate_by_byte(NEW.content, 2000);
END IF; -- title 100 byte 제한
IF NEW.title IS NOT NULL THEN
NEW.title := fn_truncate_by_byte(NEW.title, 100);
END IF; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
3. 트리거 생성
CREATE TRIGGER trg_truncate_text
BEFORE INSERT OR UPDATE
ON 테이블명
FOR EACH ROW
EXECUTE FUNCTION fn_truncate_trigger();
4. 동적(재사용) 버전
CREATE OR REPLACE FUNCTION fn_truncate_dynamic()
RETURNS TRIGGER AS $$
DECLARE
col_name TEXT := TG_ARGV[0];
max_len INT := TG_ARGV[1]::INT;
val TEXT;
BEGIN
-- 컬럼 값 가져오기
EXECUTE format('SELECT ($1).%I', col_name) INTO val USING NEW; IF val IS NOT NULL THEN
val := fn_truncate_by_byte(val, max_len); -- 다시 세팅
EXECUTE format('SELECT ($1).%I := $2', col_name)
USING NEW, val;
END IF; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
👉 사용
CREATE TRIGGER trg_truncate_content
BEFORE INSERT OR UPDATE ON 테이블명
FOR EACH ROW
EXECUTE FUNCTION fn_truncate_dynamic('content', '2000');
실무 핵심 정리
- 한글/이모지 깨짐 없이 안전하게 자름
- LMS(2000 byte) 대응 가능
- DB에서 자동 처리 → 개발 실수 방지
- Java에서도 동일 로직 있으면 더 안전 (이중 방어)
주의
- 루프 방식이라 대량 처리 시 약간 비용 있음
→ 일반 CRUD에서는 거의 문제 없음 - 초고속 필요하면 → Java에서 먼저 자르고 DB는 검증만
원하면 다음 단계로 가능
- byte truncate + “…” 붙이기
- 로그 남기면서 truncate
- 여러 컬럼 설정 테이블 기반 자동 처리



