DB 스키마에 unique 키가 걸려있는 컬럼에 중복된 값이 저장되거나 검색 결과에 나와야 할 값이 누락된 경험이 있다면 유니코드를 의심해보자.
한글과 유니코드
이 문제에대해 알아보기에 앞서 한글과 유니코드의 관계를 알아야 하는데 정리가 잘 되어있는 아래 링크를 참고하자.
https://gist.github.com/Pusnow/aa865fa21f9557fa58d691a8b79f8a6d
전세계 문자들을 컴퓨터에 표현하기 위해 아스키코드, 유니코드라는 코드표를 만들어 이렇게 쓰기로 약속을 하였다. 그리고 이 코드표를 참고하여 여러가지 방식으로 값을 표현할수 있다(인코딩). 웹에선 현재 UTF-8 인코딩이 대세이며 대부분 이것으로 통일하여 쓴다.
그리고 한글의 경우 보통 조합형(NFD) 과 완성형(NFC)으로 나타낼수 있는데 OS 에 따라 사용하는 방식이 다르고 사람이 겉보기엔 같은 글자이지만 속은 전혀 다른 바이너리 값으로 이루어져 있을수도 있다. (컴퓨터 입장에선 다른 값.)
맥 - 조합형 (NFD)
윈도우, 리눅스 계열 - 완성형 (NFC)
문제점
윈도우 사용자와 맥 사용자간 이메일을 통해 첨부파일을 주고 받을때 윈도우 사용자는 맥 사용자간 첨부한 한글 파일명이 깨져서 보이는 경우가 종종있는데 위와 같은 이유 때문이다. (한글의 경우 예전에 많은 논의와 논쟁이 있었고 완성형으로 가닥이 잡혀 완성형이 표준이다.)
이러한 문제가 개발로 넘어오면 같은 문자가 DB 에 중복되어서 저장된다거나 검색 결과에서 누락되는 현상이 발생할수도 있다. 이러한 현상을 방지하기 위해 유니코드 정규화를 해주는것이 좋다. 그렇지 않으면 추후 데이터 처리시 골치가 아파질수있다..
실제 값을 보며 NFC 와 NFD 의 차이를 확인해보자. (PHP 로 작성)
var_dump(
'조합형(NFD): ' . bin2hex('한글'),
'완성형(NFC): ' . bin2hex('한글')
);
// 결과
"조합형(NFD): e18492e185a1e186abe18480e185b3e186af"
"완성형(NFC): ed959ceab880"
맥에서 파일명 처리시 NFD 로 하기때문에 MAC 의 파일명을 복붙하면 NFD 로 처리된 문자열을 얻을수있다.
UTF-8 로 인코딩된 한글을 16진수로 바꿔보면 위와 같은 결과를 얻을수 있고 UTF-8 인코딩의 경우 아스키코드는 1바이트 한글은 3바이트로 표현한다. 그리고 각 글자가 16진수로 어떻게 표현되고 있는지 확인해보면 아래와 같다.
조합형(NFD) - 한글자모 유니코드 블록
유니코드 표 - http://unicode.org/charts/PDF/U1100.pdf
ㅎ: e18492
ㅏ: e185a1
ㄴ: e186ab
ㄱ: e18480
ㅡ: e185b3
ㄹ: e186af
완성형 (NFC) - 한글 음절 유니코드 블록
유니코드 표 - https://unicode.org/charts/PDF/UAC00.pdf
한: ed959c
글: eab880
조합형은 자모를 각각 나타내기 때문에 완성형 보다 사이즈가 더 크기도 하고 한글 정렬에서 결과가 이상하게 나올수도 있다. (받침이 있을 경우 "각"이라는 단어가 "가나" 라는 단어보다 앞에 나올수도 있다.)
다음은 위 값을 저장할 디비 스키마를 만들어보자.
CREATE TABLE `unicode_test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unicode_test_name_unique` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
'name' 컬럼에 유니크 키를 걸어 중복된 값이 저장될수 없게 하였지만 위 값을 저장했을시 다음과 같이 둘다 저장되는것을 볼 수가 있다.
그리고 쿼리 검색 결과에서도 조합형 한글은 누락되어 다음과 같이 나온다.
해결방법
유니코드 정규화를 통해 처리 방식을 하나로 통일하면 된다. 다행히 유니코드 정규화에 관해선 각 언어들에서 지원해주는 함수들이 있기에 이를 활용하자.
JS
const name = '한글';
const name2NFC = name.normalize('NFC');
PHP
normalizer_normalize('한글', Normalizer::FORM_C)
JAVA
import java.text.Normalizer;
Normalizer.normalize("string", Normalizer.Form.NFC);
'IT > 이것저것' 카테고리의 다른 글
[ChatGPT] GPTs 커스텀 플러그인 만들기 (0) | 2024.04.04 |
---|---|
[문서 생성기] 문서 생성 오픈소스 알아보기 (1) | 2024.03.12 |
[ChatGPT] ChatGPT 더 잘 쓰는 법 prompt 패턴 (1) | 2023.11.13 |
[캐시] 캐시에 대하여 (0) | 2023.02.14 |
[카카오 로그인 연동] 카카오 로그인 연동 에러 해결방법 Admin Settings Issue (KOE101) (0) | 2023.01.21 |