utf8

배경

데이터베이스 테이블에서 emoji등 문자열을 저장해야하는 컬럼은 대부분 utf8mb4로 타입을 명시했다. utf8mb3는 emoji를 저장할 수 없기때문에 4바이트까지 저장할 수 있는 변수형을 사용해야한다는 것이다.

  • 그렇다면 utf8은 어떻게 3바이트를 저장하고 utf8mb4는 4바이트인걸까?
  • 이모지를 사용하기 위해서는 4바이트가 필요하다고 한다. 이모지가 4바이트를 차지하는지 어떻게 아는가?

문자열과 이모지를 저장하기 위해 컬럼 타입을 utf-8mb4로 설정하고 간단하게 끝내는 것은 마치 이야기의 엔딩만 고치고 끝난거 같아서 이전까지 '알았다고 생각했던' 문자 인코딩에 대해서 한번 더 짚고 넘어가고 싶었다. 

ASCII

American Standard Code for Information Interchange
키보드의 키들이 각각 7 bit 이진수에 대응되었다. 0~127번 asciicode와 문자들이 매핑되었고 문자의 중요도, 사용성으로 순서를 배치했다. 특히 중요도와 사용성까지 고려한 부분은 A ascii 65, a ascii 97로 알 수 있다. 

https://theasciicode.com.ar/

처음 프로그래밍 할때 알파벳 문자의 ascii코드 번호 출력하는 예제가 떠오른다. 그때는 그저 A 문자는 ascii 코드 65라는 것에만 집중했던거 같다... 그리고 소문자의 ascii 코드도 출력하는 예제를 구현할때는 대문자 Z다음 소문자 a가 이어질거라 생각했는데 아니었던 것에 당황했던 기억이 난다. 대문자 A와 소문자 a가 32만큼 차이난다는 점은 그냥 그렇구나 하고 넘어갔다.
A의 값인 65를 7bit 이진수로 표현한다면 1000001이다. 소문자 a의 ascii 코드 값 97는 1100001로 대문자 소문자 각각 첫번째 알파벳의 끝부분 비트가 001로 맞춰지기 때문에 이후 25자 알파벳들을 계산할때 몇번째 문자인지 인식하기 더 편할 수 있다.
알파벳과 사용되는 발음 기호들을 표현하기 위해 1bit를 추가해서 1 byte로 extended ascii로 256 문자를 표현할 수 있었다. 알파벳을 사용하는 지역에서는 문제 없이 문자 데이터를 사용할 수 있었고 다른 문자 기호를 사용하는 나라에서는 다르게 매칭하여 코드체계를 활용하였다. 

characterBemore
ascii6610177111114101
byte010000100110010101001101011011110111001001100101

 

하지만...

서로 다른 지역에서 문서를 주고받을때 표준 체계가 다르다보니 다양한 문자 집합과 인코딩이 존재하여 국제 간 텍스트 교환시 문자가 깨지게 되는 어려움이 존재했다. 서로 다른 문자 인코딩 시스템 간 호환성이 지원되는 문자 인코딩 표준 체계 변경이 필요했다. 

 

Unicode

위 문제를 해결하기 위해 등장한 unicode에서는 모든 문자에 고유한 코드 포인트를 할당하는 표준을 제공했다. ascii에서 한 문자가 어떻게 생기든 하나의 코드에 대응한다면 유니코드에서는 한 문자를 기존의 문자에서 변형되는 부분이나 여러 기호의 조합으로 보고 이에 대한 코드 포인트들을 조합하는 식이다.
"글" 이라는 한글 문자를 unicode는 어떻게 인식하는지 예시로 볼 수 있다. 

 ㄱ(U+1100)ㅡ(U+1173)ㄹ(U+11AF)
utf-80xEA0xB80x80

 
이러한 조합을 16진수로 변환하는 여러 유니코드 부호화 형식(Unicode Transformation Format)을 지원한다.

 

UTF-32

고정 길이 인코딩 방식으로 모든 문자를 4byte로 표현한다. 이 경우 기존 ascii로도 충분히 표현할 수 있는 정도의 내용도 4배 공간을 차지하게 된다. 모든 문자에 동일한 크기가 할당되어서 특정 문자의 인덱스를 계산하는데 용이하다. 이를 통해 특정 문자에 빠르게 접근할 수 있습니다. 즉, 일관된 접근성과 쉬운 문자 인덱싱을 제공하기 위해 고정 길이 인코딩을 지원한다.

characterBemore
Hex/UTF-3200420065006D006F00720065

UTF-8

코드 포인트를 1~4바이트로 가변적으로 매핑할 수 있다. 따라서 UTF-32방식보다 더 적은 저장공간을 쓴다. 기존의 ascii 코드와의 호환성을 제공하여 기존 작성된 문서나 소스 코드를 그대로 사용할 수 있다. 따라서 텍스트가 영어와 같은 ascii 문자로 이루어져 있다면 효율적인 파일 크기와 네트워크 전송을 보장한다. 이때 기존 알파벳 문자들은 다른 문자들보다 더 적은 바이트를 쓰는 것을 볼 수 있는데 다른 언어권은 그럼 더 많은 저장공간을 사용해야 되는건가에 대한 의문이 들 수 있다. 그렇다...UTF-32가 모든 언어, 기호에 공평하게 4바이트라는 공간을 쓰게 한다면 UTF-8에서는 영문일때 더 저장공간을 줄일 수 있는 특징이 있다.

characterBemore
UTF-842656D6F7265

 

In Database

MySQL에서는 utf8을 3 bytes 기반의 자료형으로 설계하여 UTF-8을 지원했다고 한다(참고 블로그). 따라서 이모지 문자를 변환하기 위해서는 4바이트가 필요한데 utf8은 일부만 지원하여 데이터 손실같은 문제로 에러가 발생하게 된다. 즉, 위에서 설명한 UTF-8 그대로 데이터베이스에서 쓰이는 utf8이 동일하게 4바이트까지 쓸 수 있도록 설정하기 위해서는 utf8mb3이 아니라 utf8mb4 타입으로 명시해야 기존 특수 문자열보다 1바이트 더 쓰는 이모지 등을 저장할 수 있는 것이다.
 
보충 문자(서로 다른 Unicode 코드 포인트로 이루어진 길이가 2인 문자)의 경우, utf8mb4는 이를 저장하기 위해 4 바이트가 필요하며, utf8mb3는 이 문자를 전혀 저장할 수 없다. 
UTF-8의 자체 개념에서 애초에 emoji까지 커버할 수 있는 구조였는데 이를 MySQL에서 가변길이를 제한한 것인지... 처음부터 4바이트로 제공하지 않은 이유가 궁금하다. 역시 저장 공간적 측면에서 낭비를 줄이기 위해 BMP(기본 다국어 평면)내 문자만 처리할 수 있는 타입이면 충분하다고 설계한 것일까 (My SQL's UTF-8 Isn't Real) 사용자들의 혼란을 인식한건지 MySQL 8.0.28이후 부터는 더이상 utf8을 utf8mb3의 별칭으로 사용하지 않는다고 한다.
 
 
 
참고 자료
- https://www.youtube.com/watch?v=MijmeoH9LT4
- https://www.youtube.com/watch?v=uTJoJtNYcaQ&t=59s
- https://www.youtube.com/watch?v=ut74oHojxqo
- https://www.compart.com/en/unicode/U+AE00
- https://miaow-miaow.tistory.com/37

'CS' 카테고리의 다른 글

[fluent python]함수 데코레이터와 클로저  (0) 2024.04.14
[python] 일급 함수 디자인 패턴  (0) 2024.03.31