AWS RDS 한글 깨짐 현상 분석 및 파라미터 그룹 최적화

컬 개발 환경에서는 정상적으로 동작하던 애플리케이션이 AWS RDS(Relational Database Service) 배포 후 한글 데이터가 물음표('???')로 표시되거나 외계어(Mojibake) 형태로 깨지는 현상은 초기 구축 단계에서 빈번하게 발생하는 기술 부채 중 하나입니다. 이는 단순히 한글 폰트의 문제가 아니라, 클라이언트에서 데이터베이스 엔진, 그리고 스토리지 계층에 이르기까지 데이터가 흐르는 파이프라인 상의 인코딩(Character Set)정렬 규칙(Collation)의 불일치에서 기인합니다. 특히 AWS RDS의 기본 파라미터 그룹 설정은 대부분 `latin1`을 기본값으로 채택하고 있어, 명시적인 오버라이드 없이는 멀티바이트 문자 처리에 실패하게 됩니다. 본 아티클에서는 이러한 인코딩 미스매치의 근본 원인을 아키텍처 관점에서 분석하고, MySQL 및 PostgreSQL 엔진별 엔지니어링 해결책을 다룹니다.

1. 인코딩 파이프라인과 핸드셰이크 메커니즘

데이터베이스의 인코딩 문제는 단일 설정이 아닌 계층적 구조에서 발생합니다. MySQL을 예로 들면, 데이터가 저장될 때 다음 6가지 단계의 변수를 검증해야 합니다. 이 중 하나라도 `utf8mb4`가 아닌 다른 값(주로 `latin1`)으로 설정되어 있다면 변환 과정에서 손실이 발생합니다.

Character Set Layer Hierarchy:
  1. Server: DB 서버의 기본 인코딩
  2. Database: 특정 스키마의 기본 인코딩 (Server 설정을 상속)
  3. Table: 테이블의 기본 인코딩 (Database 설정을 상속)
  4. Column: 실제 데이터가 저장되는 컬럼의 인코딩 (Table 설정을 상속)
  5. Client & Connection: 애플리케이션과 DB 간 세션의 인코딩

가장 흔한 오류 패턴은 서버와 DB는 `utf8mb4`로 설정했으나, 클라이언트와 서버가 연결을 맺는 Connection 단계에서 `latin1`으로 핸드셰이크가 일어나는 경우입니다. 이 경우 애플리케이션은 UTF-8 데이터를 보내지만, DB는 이를 Latin1 바이트 스트림으로 해석하여 잘못된 매핑을 수행합니다.

2. MySQL/MariaDB 파라미터 그룹 최적화전략

AWS RDS for MySQL의 경우, 콘솔에서 '파라미터 그룹(Parameter Group)'을 생성하여 기본 설정을 덮어써야 합니다. 단순히 `utf8`을 사용하는 것은 권장되지 않습니다. 표준 `utf8`은 가변 3바이트로 이모지(Emoji)와 같은 4바이트 문자를 저장할 수 없기 때문에, 반드시 `utf8mb4`를 사용해야 합니다.

2.1 필수 파라미터 설정

RDS 파라미터 그룹 편집기에서 다음 항목들을 찾아 변경하십시오. `collation`은 정렬 속도와 정확도 트레이드오프를 고려하여 선택해야 합니다. 일반적으로 `utf8mb4_general_ci`가 속도는 빠르지만, 현대적인 정렬 정확도를 위해 `utf8mb4_unicode_ci` 또는 8.0 버전 이상인 경우 `utf8mb4_0900_ai_ci`를 권장합니다.

Parameter Name Recommended Value Description
character_set_client utf8mb4 클라이언트로부터 들어오는 문자의 인코딩
character_set_connection utf8mb4 서버가 쿼리를 처리할 때 사용할 인코딩
character_set_database utf8mb4 생성되는 DB의 기본 인코딩
character_set_filesystem utf8mb4 파일 시스템 경로명 인코딩
character_set_results utf8mb4 쿼리 결과 또는 에러 메시지의 인코딩
character_set_server utf8mb4 서버의 기본 인코딩
collation_connection utf8mb4_general_ci 연결 시 문자열 비교 규칙
collation_server utf8mb4_general_ci 서버 기본 정렬 규칙

2.2 skip-character-set-client-handshake의 중요성

많은 엔지니어가 놓치는 부분이 `skip-character-set-client-handshake` 설정입니다. 클라이언트(애플리케이션)가 접속 시 자신의 인코딩 정보를 보내면, MySQL은 기본적으로 클라이언트의 요청을 우선시하여 세션 변수를 변경합니다. 이를 방지하고 서버 측에 설정된 파라미터 그룹 값을 강제하려면 이 옵션을 활성화해야 합니다.


-- 변경 전 확인 (AWS RDS 기본 상태일 경우 latin1 등이 섞여 있음)
SHOW VARIABLES LIKE 'char%';

/*
출력 예시 (Bad Case):
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | latin1 | <-- 문제 발생 지점
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | latin1 | <-- 문제 발생 지점
| character_set_system | utf8 |
+--------------------------+----------------------------+
*/
Reboot Required: 파라미터 그룹을 수정한 후, 변경 사항을 적용하려면 반드시 RDS 인스턴스를 재부팅해야 합니다. 운영 중인 서비스라면 트래픽이 적은 시간대를 이용하거나 Multi-AZ 페일오버를 활용하십시오.

3. PostgreSQL 인코딩 처리 메커니즘

PostgreSQL은 MySQL과 달리 인코딩 처리가 더 엄격하며, 클러스터 초기화(initdb) 시점에 결정되는 경우가 많습니다. RDS for PostgreSQL은 인스턴스 생성 시 기본적으로 `UTF8`을 선택하도록 유도하지만, `Collation`과 `Ctype` 설정에 주의해야 합니다.

  • Encoding: 데이터베이스에 저장되는 문자 집합 (반드시 UTF8 권장).
  • LC_COLLATE: 문자열 정렬 순서 (예: `ko_KR.UTF-8` 사용 시 한글 가나다순 정렬).
  • LC_CTYPE: 문자 분류 (대소문자 구분 등).

PostgreSQL에서 인코딩 문제가 발생하는 주된 원인은 클라이언트 인코딩(client_encoding) 설정입니다. 서버는 UTF8이어도 클라이언트가 SQL_ASCII 등으로 접속하면 변환 오류가 발생할 수 있습니다.


-- 현재 클라이언트 인코딩 확인
SHOW client_encoding;

-- 세션 레벨에서 강제 설정 (임시 조치)
SET client_encoding TO 'UTF8';

애플리케이션단(JDBC, Node.js Driver 등)에서 접속 URL 파라미터에 인코딩을 명시하는 것이 가장 확실한 예방책입니다. 예를 들어 JDBC URL에는 `?charSet=UTF-8`을 추가합니다.

4. 이미 깨진 데이터의 진단 및 복구

이미 `???`로 데이터가 들어간 경우와, 이상한 외계어(`안ë…`)로 보이는 경우는 대응 방법이 다릅니다.

4.1 '???' (Question Marks)로 저장된 경우

Unrecoverable Error: 데이터베이스 필드에 '?' 문자가 리터럴로 저장된 경우입니다. 이는 인코딩 변환 과정에서 매핑되지 않는 문자를 대체 문자(Replacement Character)로 치환해버린 것으로, 원본 바이트 정보가 유실되었기 때문에 복구가 불가능합니다. 소스 데이터를 다시 적재해야 합니다.

4.2 Mojibake (외계어)로 저장된 경우

데이터가 `latin1` 필드에 `utf8` 바이트 스트림 그대로 구겨져 들어간 경우입니다. 바이트 정보는 보존되어 있으므로, 덤프 후 올바른 인코딩으로 다시 주입하거나 `CAST` 함수를 통해 복구할 수 있습니다. MySQL의 경우 `BINARY`로 캐스팅 후 다시 `utf8mb4`로 변환하는 방식을 시도해볼 수 있습니다.


-- Mojibake 복구 시도 (컬럼 단위)
-- 1. 현재 컬럼을 binary 형태로 변경 (바이트 보존)
ALTER TABLE users MODIFY name VARBINARY(255);

-- 2. binary 컬럼을 올바른 utf8mb4 캐릭터 셋으로 변경
ALTER TABLE users MODIFY name VARCHAR(255) CHARACTER SET utf8mb4;

이 작업은 데이터 손실 위험이 있으므로, 반드시 스냅샷 생성 또는 테이블 백업 후 진행해야 합니다. 또한, 테이블 전체의 인코딩을 변경할 때는 다음 명령어를 사용합니다.


-- 테이블의 기본값 변경 (기존 데이터는 변환되지 않음)
ALTER TABLE table_name DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 테이블 및 기존 데이터까지 모두 변환 (Blocking Operation 주의)
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
Zero-Downtime Migration: 대용량 테이블에 대해 `CONVERT TO`를 실행하면 테이블 락(Table Lock)이 발생하여 서비스 장애로 이어질 수 있습니다. 이 경우 `pt-online-schema-change`나 `gh-ost` 같은 도구를 사용해야 합니다.

5. 결론 및 베스트 프랙티스

RDS의 한글 인코딩 문제는 발생 후 해결하는 것보다, 인프라 프로비저닝 단계에서 예방하는 것이 비용 효율적입니다. Terraform이나 CloudFormation 같은 IaC(Infrastructure as Code) 도구를 사용하여 RDS 파라미터 그룹을 코드로 관리하고, `utf8mb4` 설정을 표준 템플릿화하십시오.

또한, 애플리케이션의 데이터베이스 연결 풀(HikariCP 등) 설정과 ORM(JPA, TypeORM) 설정에서도 인코딩을 명시적으로 지정하여, 전체 파이프라인의 정합성을 유지해야 합니다. 데이터베이스는 데이터의 최종 저장소입니다. 입구(Client)부터 출구(Storage)까지 단 하나의 레이어도 `latin1`과 같은 레거시 설정이 개입할 틈을 주어서는 안 됩니다.

Post a Comment