글자수 계산이 생각보다 복잡한 이유

한 글자가 몇 바이트인지, 이모지가 글자 수에 어떻게 계산되는지, 한글과 영문의 바이트 수가 다른 이유는 무엇인지 — 이 질문들에 답하려면 유니코드와 인코딩 시스템을 이해해야 합니다.

글자수 계산은 간단해 보이지만, 실제로는 "어떤 기준으로 세느냐"에 따라 결과가 크게 달라집니다. SNS 플랫폼 제한, 데이터베이스 컬럼 크기, API 페이로드 제한 등 각 맥락마다 다른 계산 기준이 적용됩니다.

유니코드와 인코딩

유니코드(Unicode)는 전 세계 모든 문자에 고유 번호(코드 포인트)를 부여하는 표준입니다. 예를 들어 '가'는 U+AC00, 'A'는 U+0041, '😀'는 U+1F600입니다. 현재 유니코드는 100만 개 이상의 코드 포인트를 정의하고 있습니다.

인코딩은 유니코드 코드 포인트를 실제 바이트로 저장하는 방법입니다. 가장 널리 사용되는 인코딩은 UTF-8로, 각 코드 포인트를 1~4바이트로 가변 길이 인코딩합니다.

UTF-8 바이트 크기

UTF-8에서 문자별 바이트 수는 다음과 같습니다. ASCII 문자(영문, 숫자, 기본 기호)는 1바이트, 라틴어 확장·그리스어·키릴 문자 등은 2바이트, 한국어·한자·일본어·이모지 중 일부는 3바이트, 이모지 대부분·희귀 한자 등은 4바이트를 사용합니다.

실무적으로 중요한 것은 한글 한 글자가 3바이트라는 점입니다. "안녕하세요"는 5글자이지만 15바이트입니다. 데이터베이스에서 VARCHAR(100)이라면 한글은 33자까지만 저장 가능합니다(MySQL utf8mb4 기준).

// JavaScript에서 UTF-8 바이트 계산
const text = "안녕하세요";
const byteLength = new TextEncoder().encode(text).length;
console.log(byteLength); // 15 (한글 3바이트 × 5글자)

// Python
import sys
text = "안녕하세요"
byte_length = len(text.encode('utf-8'))
print(byte_length)  # 15

이모지 글자수의 함정

이모지는 글자수 계산에서 가장 까다로운 영역입니다. 여러 가지 복잡성이 있습니다.

서로게이트 쌍(Surrogate Pair): U+FFFF를 넘는 코드 포인트(대부분의 이모지)는 JavaScript에서 2개의 UTF-16 코드 유닛으로 표현됩니다. 따라서 JavaScript의 string.length는 이모지를 2로 계산합니다.

console.log("😀".length); // 2 (서로게이트 쌍)
console.log([..."😀"].length); // 1 (스프레드 연산자로 코드 포인트 기준 계산)
// Array.from("😀").length도 1

ZWJ 시퀀스: 여러 이모지를 ZWJ(Zero Width Joiner, U+200D)로 연결하면 하나의 이모지로 표시됩니다. 예를 들어 가족 이모지 👨‍👩‍👧‍👦는 실제로 4개의 이모지와 3개의 ZWJ로 구성된 11개 코드 포인트입니다. JavaScript의 string.length로는 훨씬 큰 수로 계산됩니다.

피부톤 수정자: 👍🏽처럼 피부톤을 포함한 이모지도 기본 이모지 + 수정자 코드 포인트 2개로 구성됩니다.

이러한 복잡성 때문에 이모지를 포함한 텍스트의 "글자수"는 어떤 API를 사용하느냐에 따라 다른 결과를 보입니다. 최신 JavaScript에서는 Intl.Segmenter를 사용하면 사람이 인식하는 글자(grapheme cluster) 단위로 정확히 계산할 수 있습니다.

const segmenter = new Intl.Segmenter();
const text = "👨‍👩‍👧‍👦 가족";
const segments = [...segmenter.segment(text)];
console.log(segments.length); // 3 (이모지 1 + 공백 1 + 가족 2글자)

플랫폼별 글자 제한 기준

각 플랫폼은 서로 다른 기준으로 글자 수를 측정합니다.

X(트위터): 280자 — 코드 포인트 기준. 대부분의 한·중·일 문자는 2자로 계산(140자 제한이었던 역사 때문). 이모지도 2자로 계산.

Instagram: 2,200자 — 바이트 또는 문자 기준(정확한 스펙은 비공개). 해시태그는 별도 제한(30개).

Google 메타 description: 155~160자 — 픽셀 너비 기준. 영문 대문자는 소문자보다 더 많은 픽셀을 차지하므로 가변적.

SMS: 160자(영문) / 70자(한글) — GSM 7비트 인코딩 기준. 한글은 UCS-2를 사용하므로 단문 기준이 70자.

MySQL VARCHAR(n) — utf8mb4에서는 최대 4바이트/문자. VARCHAR(255)는 한글 약 85자, 이모지 약 63자.

개발 시 주의사항

JavaScript string.length의 한계: string.length는 UTF-16 코드 유닛 수를 반환하므로, 이모지나 희귀 문자에서 예상과 다른 결과를 줄 수 있습니다. 사용자에게 보여줄 글자수는 Intl.Segmenter 또는 [...str].length(코드 포인트 기준)를 사용하세요.

데이터베이스 컬럼 크기: VARCHAR(100)은 바이트가 아닌 문자 수(문자셋에 따라 다름) 기준입니다. MySQL의 utf8mb4 문자셋에서는 최대 4바이트/문자로 총 400바이트까지 허용됩니다. 이모지를 저장하려면 반드시 utf8mb4(4바이트 지원)를 사용해야 합니다. 기존 utf8은 3바이트까지만 지원하여 이모지 저장 시 오류가 발생합니다.

API 페이로드 제한: REST API의 요청 본문 크기 제한은 보통 바이트 단위입니다. 한글 텍스트를 다루는 API에서는 글자수가 아닌 바이트 크기로 제한을 계산하고 검증해야 합니다.

줄바꿈 문자 처리

줄바꿈 문자도 글자수 계산에서 간과하기 쉬운 요소입니다. LF(\n, Unix/macOS)는 1바이트, CRLF(\r\n, Windows)는 2바이트입니다. Windows에서 작성한 텍스트를 Linux 서버에 저장하면 줄바꿈 수만큼 바이트가 추가됩니다. Git의 core.autocrlf 설정이 이 문제를 처리합니다.

자주 묻는 질문

한글 "가"는 몇 바이트인가요?

UTF-8에서 한글 한 글자는 3바이트입니다. 완성형 한글 11,172자(가~힣)는 모두 U+AC00~U+D7A3 범위에 있으며, UTF-8에서 3바이트 범위(E0~EF로 시작)에 해당합니다. 참고로 영문(ASCII)은 1바이트, 이모지는 4바이트입니다.

JavaScript에서 이모지가 2글자로 계산되는 이유는?

JavaScript 문자열은 UTF-16으로 인코딩되어 있고, string.length는 UTF-16 코드 유닛 수를 반환합니다. U+FFFF를 초과하는 대부분의 이모지는 UTF-16에서 서로게이트 쌍(2개의 코드 유닛)으로 표현되어 length가 2로 계산됩니다. 코드 포인트 기준 1글자로 세려면 [...str].length 또는 Array.from(str).length를 사용하세요.

SNS 플랫폼마다 글자수 계산이 다른 이유는?

각 플랫폼이 내부적으로 사용하는 인코딩과 계산 기준이 다르기 때문입니다. X는 코드 포인트 기준이지만 한중일 문자는 2자로 가중치를 부여합니다. Instagram은 자체 기준을 사용합니다. Google의 meta description은 픽셀 너비 기준이라 같은 글자수여도 폰트에 따라 다르게 표시될 수 있습니다. 실제 발행 전에 해당 플랫폼에서 직접 테스트하는 것이 가장 정확합니다.

MySQL에서 이모지 저장 시 오류가 나는 이유는?

MySQL의 utf8 문자셋은 실제로는 최대 3바이트 문자만 지원하는 불완전한 구현입니다. 4바이트가 필요한 이모지(대부분)를 저장하려면 utf8mb4 문자셋을 사용해야 합니다. 테이블과 컬럼 모두 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci로 설정하고, MySQL 연결 문자셋도 utf8mb4로 지정해야 합니다.

도구 활용

텍스트의 글자수, 바이트 수, 단어 수를 즉시 확인하려면 글자수 세기 도구를 사용하세요. 공백 포함/제외, UTF-8 바이트 계산, 줄 수 등을 실시간으로 확인할 수 있습니다.

광고 영역