About VATOS
home
Company
home

techlog_3

← Tech Insights

CHAR/N 타입 변환의 전략

CHAR, NCHAR, NVARCHAR 계열 타입이 가진 공백 패딩과 인코딩 맥락을 어떻게 이관해야 하는지 정리합니다.

CHAR와 NVARCHAR/NCHAR는 가장 단순해 보이지만, 실무에서는 자주 복병이 되는 타입입니다. 둘 다 문자열이고, 대부분의 이관 도구는 자동으로 변환해주며, 겉으로 보기에는 데이터도 정상적으로 들어가기 때문에 이 타입들은 종종 "신경 쓰지 않아도 되는 영역"으로 취급됩니다.

하지만 CHAR의 공백 패딩은 PostgreSQL에서 조용한 장애를, NVARCHAR의 인코딩 계약은 외부 연계 단절을 만들어냅니다. 문제는 타입 자체가 아니라, 이 타입이 과거 시스템에서 어떤 약속을 담고 있었는지를 알 수 없다는 점입니다.

이 문서는 메타데이터 변환에 집중합니다. 데이터를 어떻게 옮길 것인가가 아니라, 타입 정의를 어떻게 바꿀 것인가가 핵심입니다. CHAR → VARCHAR 전환이 데이터 값은 그대로 유지하더라도, 비교 방식, 저장 구조, 애플리케이션 동작을 바꿀 수 있습니다. 이 문서의 목적은 단 하나입니다. 언제 타입을 바꿔도 되는지, 언제 바꾸면 안 되는지를 판단할 수 있게 하는 것.

CHAR: 고정 길이가 만드는 공백

CHARVARCHAR와 달리 고정 길이 타입으로서 CHAR(10)으로 정의하면 무조건 10바이트(또는 10문자)를 차지합니다. 만약 'A'를 저장하면 1글자만 입력했지만, 나머지 9바이트는 공백으로 채워지는데 이것을 공백 패딩(Blank Padding)이라고 합니다.

CHAR는 모든 데이터베이스에서 동일하게 동작한다?

DB 내부에서는 그렇습니다. 하지만 애플리케이션 레벨에서는 아닙니다. CHAR 타입은 Oracle, PostgreSQL, 그리고 MySQL의 기본적인 설정에서는 비교 연산 시(=, <, >, IN 등) trailing space를 무시합니다. 다만 SELECT 결과로 반환되는 값이나 LENGTH() 같은 함수에서는 trailing space가 포함됩니다.

이로 인해 데이터베이스 내부 비교는 일관된 결과를 제공하지만, 다음 영역에서는 주의가 필요합니다:

  • 애플리케이션 바인딩: JDBC/ODBC로 읽은 값에는 trailing space가 포함되므로, 애플리케이션에서 equals() 비교 시 불일치 발생. trim() 처리 필요
  • ORM 매핑: JPA, MyBatis 등의 문자열 처리 동작 확인 필요
  • 문자열 함수: CONCAT, SUBSTR 등에서 공백 포함 동작
  • 외부 출력: JSON, CSV, API 응답에 공백 포함될 수 있음
  • MySQL collation: PAD SPACE vs NO PAD 설정에 따라 비교 동작 자체가 달라질 수 있음

아래 표는 CHAR(10) 컬럼 기준으로 DB 내부 비교 연산을 정리한 것입니다.

케이스입력/조건OracleMySQLPostgreSQL
1) 동일 비교'a' = 'a 'TRUETRUETRUE
2) WHERE 매칭col='a ' 인 row가 있을 때 <br>WHERE col='a'매칭됨(1 row)매칭됨(1 row)매칭됨(1 row)
3) JOIN 매칭A.key='a', B.key='a '로 <br/>JOIN매칭됨(1 row)매칭됨(1 row)매칭됨(1 row)
4) GROUP BY값이 'a' 1건, 'a ' 1건일 때 <br/>GROUP BY col1그룹1그룹1그룹
5) DISTINCT값이 'a' 1건, 'a ' 1건일 때 <br/>SELECT DISTINCT col1개1개1개
6) UNIQUE 제약UNIQUE(col)에서 'a' 이후<br/> 'a ' INSERT충돌(실패)충돌(실패)충돌(실패)

표에서 알 수 있듯이, CHAR 타입은 데이터베이스 내부 비교에서 trailing space를 무시합니다. 따라서 CHAR는 다른 문자열 타입에 비해 DB 내부 연산의 이관 리스크가 낮은 편이지만, 애플리케이션 레벨이나 외부 연계에서는 검토가 필요합니다.

일반적으로 Source에서 CHAR로 정의된 컬럼은, Target에서도 CHAR로 유지합니다. 데이터베이스 내부 비교 동작은 일관되므로, SQL 문법 검토 우선순위를 낮출 수 있습니다.

단, 다음 영역은 반드시 검토해야 합니다:

  • 애플리케이션 레벨 문자열 비교 (equals(), trim() 필요 여부)
  • 외부 시스템 연계 (고정 길이 파일, 전문 통신)
  • JSON/REST API 응답 (클라이언트에서 trailing space 처리)
  • ORM 매핑 (JPA, MyBatis 등의 문자열 비교 동작)

#### CHARCHAR로 유지해야 하는가?

실무에서는 많은 프로젝트가 CHARVARCHAR로 전환합니다. 이유는 다음과 같습니다:

  • 자동 변환 툴의 기본 설정 (가장 흔한 원인)
  • 저장 공간 절약
  • 막연한 "베스트 프랙티스"

흥미로운 점은 실무에서 가장 많이 사용되고 있는 AWS SCT(Schema Conversion Tool)나 Ora2Pg 같은 대표적인 이기종 마이그레이션 도구들이 CHAR 타입을 대하는 태도입니다. 이런 이기종 마이그레이션 도구들이 Oracle의 CHAR를 PostgreSQL의 VARCHAR로 변환하도록 권장하는 이유는, CHAR 타입의 공백 패딩 자체 때문이라기보다 PostgreSQL 환경에서 CHAR가 애플리케이션 레벨까지 공백을 노출시키기 때문이라고 보고 있기 때문입니다. 의미 보존만을 기준으로 하면 CHAR → CHAR 변환이 가장 정확한 매핑이지만, 실제 운영 환경에서는 애플리케이션, ORM, 외부 연계, API 응답 등에서 trailing space로 인한 문제가 빈번하게 발생할 수 있습니다.

운영자는 'DB 의미'가 아니라 '시스템 전체'를 관장하는 사람입니다. 비록 CHAR → CHAR 가 의미 보존 측면에서는 가장 자연스럽다 할지라도, 애플리케이션 계층에서의 문제가 발생할 소지가 있기 때문에 장기적인 운영 안정성 측면에서는 리스크가 크다고 판단하는 경우가 많습니다.

이로 인해 실무에서는 CHAR → CHAR 변환을 제한적인 코드성 컬럼에만 적용하고, 대부분은 CHAR → VARCHAR 전환과 함께 trailing space를 제거하는 전략을 기본으로 선택하는 경우가 많습니다


VARCHAR는 데이터베이스마다 다르게 동작한다

VARCHAR는 trailing space를 처리하는 방식이 데이터베이스마다 다릅니다. 위의 CHAR 표를 VARCHAR(10)으로 다시 작성하면 다음과 같습니다

케이스입력/조건OracleMySQLPostgreSQL
1) 동일 비교'a' = 'a 'TRUETRUEFALSE
2) WHERE 매칭col='a ' 인 row가 있을 때 <br>WHERE col='a'매칭됨(1 row)매칭됨(1 row)매칭안됨(0 row)
3) JOIN 매칭A.key='a', B.key='a '로 <br/>JOIN매칭됨(1 row)매칭됨(1 row)매칭안됨(0 row)
4) GROUP BY값이 'a' 1건, 'a ' 1건일 때 <br/>GROUP BY col1그룹1그룹2그룹
5) DISTINCT값이 'a' 1건, 'a ' 1건일 때 <br/>SELECT DISTINCT col1개1개2개
6) UNIQUE 제약UNIQUE(col)에서 'a' 이후<br/> 'a ' INSERT충돌(실패)충돌(실패)허용(성공)

*MySQL은 collation 설정에 따라 다를 수 있음

Source에서 CHAR(1)'A'를 저장하면 실제로는 'A '(공백 포함)로 저장됩니다. 이관 시 이 데이터를 Target의 VARCHAR로 옮기면

  • Oracle CHAR → Oracle VARCHAR: 문제 없음 (VARCHAR도 공백 무시)
  • Oracle CHAR → MySQL VARCHAR: 대부분 문제 없음 (collation 주의)
  • Oracle CHAR → PostgreSQL VARCHAR: 문제 발생 (공백 포함 비교)

CHAR에서는 호환성이 완벽했던 것이, VARCHAR로 바꾸는 순간 깨지는 것을 알 수 있습니다.

MySQL에서의 collation 설정이 CHAR → VARCHAR 전환에 미치는 영향

MySQL은 문자 비교 동작이 데이터 타입뿐 아니라 collation의 PAD SPACE / NO PAD 속성에 의해 결정됩니다. Oracle의 CHAR 컬럼을 MySQL의 VARCHAR로 변환할 경우, PAD SPACE collation에서는 기존 Oracle과 유사한 결과가 나올 수 있지만, NO PAD collation에서는 trailing space가 값으로 인식되어 비교·조인·제약조건 동작이 달라질 수 있습니다

따라서 Oracle → MySQL 이관 시 CHAR → VARCHAR 전환을 선택한다면, collation 특성과 trailing space 처리 전략(RTRIM 여부)을 함께 설계해야 합니다. 그렇지 않으면 이관 이후 결과 불일치 문제가 발생할 수 있습니다.


보이지 않는 공백이 만든 보이는 장애

실제 프로젝트에서 발생한 사례입니다.

Oracle에서 PostgreSQL로 이관하면서, 자동 변환 툴의 기본 설정에 따라 모든 CHAR 컬럼을 VARCHAR로 전환했습니다. 회원 테이블의 상태 코드(status)도 CHAR(1)에서 VARCHAR(1)로 바뀌었습니다. 담당자는 "둘 다 문자열 타입이니까 문제없겠지" 생각했습니다.

이관은 성공했고, 건수도 일치했습니다. Source의 CHAR(1) 컬럼에 있던 'A', 'B', 'C' 값들이 모두 Target의 VARCHAR(1)로 들어갔습니다. 하지만 실제로는 'A ', 'B ', 'C '(trailing space 포함)로 저장되어 있었습니다. 문제는 애플리케이션 테스트 시에 발견되었습니다. 공백 패딩으로 인하여 일부 데이터가 조회가 되지 않고, JOIN 후의 결과값이 달라지는 현상이 발생되었기 때문입니다. 결국 오픈 2주 전에 모든 데이터의 trailing space를 제거하고, 애플리케이션 입력 로직을 수정하고, SQL 일부를 수정해야 했습니다.

#### CHAR는 어떻게 다뤄야 하는가

그럼 CHAR 타입을 전환할 때 어떻게 해야 되는지에 대해 두 가지 경우로 구분해서 정리하겠습니다.

CHAR 유지가 합리적인 경우

  • 길이가 항상 고정된 코드값
  • JOIN / WHERE 조건으로 사용되지 않음
  • 외부 시스템과 연동되지 않음
  • 신규 개발/확장 가능성이 낮음
  • collation을 고정(PAD SPACE) 할 수 있음 (MySQL Only)

이런 컬럼들은 VARCHAR로 전환했을 때 얻는 이점이 거의 없습니다

VARCHAR로 전환이 권장되는 경우

  • 사용자 입력을 받는 컬럼
  • JOIN / WHERE 조건으로 자주 사용
  • 외부 시스템과 데이터 교환
  • ORM(JPA, MyBatis 등) 사용
  • JSON / REST API 응답에 포함
  • 향후 스키마 변경 가능성 있음

위의 사례중 하나라도 일치한다면 VARCHAR로 전환을 검토하는 것이 유리합니다


#### CHAR 타입의 처리: 기술적 정답과 운영 정책을 구분하라

의미 보존 관점에서 보면 CHAR → CHAR 변환이 가장 정확한 매핑입니다. CHAR 타입은 데이터베이스 내부 비교 관점에서는 비교적 일관된 동작을 제공하며, 공백 패딩으로 인한 의미 변화도 발생하지 않습니다.

그러나 운영 관점의 기본 정책은 다릅니다. PostgreSQL과 MySQL 환경에서는 CHAR 컬럼의 공백이 애플리케이션 계층, ORM, 외부 출력(JSON, 파일, API)까지 노출될 수 있으며, 이로 인해 예기치 않은 장애가 발생하는 사례가 반복되어 왔습니다. 이 때문에 실무에서는 운영 안정성을 기본값으로 삼아 CHAR → VARCHAR 전환과 함께 공백 정규화 전략을 적용하는 경우가 많습니다.

따라서 CHAR 처리 방식은 기술적 정답이 하나로 고정되지 않습니다. 마이그레이션 착수 전에 조직의 기본 정책(CHAR 유지 vs VARCHAR 전환 + 공백 정규화)을 운영 관점에서 확정하는 것이 필요합니다


NVARCHAR/NCHAR: 과거의 유산, 현재의 선택

Oracle의 NVARCHAR2NCHAR는 유니코드 저장이라기보다는 DB의 기본 문자셋과 다른 문자셋을 사용하기 위해 존재하는 데이터 타입입니다. 과거 데이터베이스 Character Set이 EUC-KR이나 Latin1이었던 시절, 한자, 일본어, 이모지 같은 다국어 문자를 저장하려면 National Character Set(AL16UTF16)을 사용하는 NVARCHAR2/NCHAR가 필수였습니다.

하지만 이기종 마이그레이션 환경에서는 상황이 달라졌습니다. 대부분의 Source와 Target이 UTF-8을 사용하기 때문에, NVARCHAR2VARCHAR2든 모두 유니코드를 저장할 수 있습니다. 게다가 PostgreSQL과 MySQL 같은 Target 데이터베이스에는 NVARCHAR 타입 자체가 존재하지 않거나, 단순히 VARCHAR의 별칭일 뿐입니다.

Oracle의 NCHARNVARCHAR2 타입은 이기종 데이터베이스로 이관될 때 PostgreSQL이나 MySQL의 VARCHAR/TEXT 타입으로 자연스럽게 흡수되며, CHAR 타입처럼 공백 패딩으로 인한 의미 변화는 거의 발생하지 않습니다. 그래서 이관 시 NVARCHAR2는 대부분의 경우 VARCHAR/TEXT로 자연스럽게 흡수되지만, 흡수되지 않는 것은 데이터가 아니라 맥락입니다. 인코딩 계약, 포맷 계약, 연계 규격이 그것입니다

Oracle에서 NVARCHAR2는 항상 문자 단위로 동작합니다. NVARCHAR2(100)은 "100글자"를 의미합니다. 반면 VARCHAR2(100)NLS_LENGTH_SEMANTICS 설정에 따라 "100바이트" 또는 "100글자"일 수 있습니다.

다만, 해당 컬럼이 사용된 배경(레거시 문자셋 회피 목적), Oracle의 NLS 설정, 외부 연계 여부에 따라 문자열 길이 및 바이트 단위 처리 로직에 영향이 있을 수 있으므로, 타입 자체보다는 사용 맥락을 중심으로 조사하는 것이 중요합니다.


#### NVARCHAR/NCHAR: 타입이 아니라 맥락을 이관하라

NVARCHAR2NCHAR는 Oracle에서 National Character Set(일반적으로 AL16UTF16)을 사용하는 유니코드 타입입니다. 기술적으로는 VARCHAR2CHAR의 유니코드 버전이지만, 실무에서 이 타입들이 가진 의미는 단순한 "문자 저장 방식"을 넘어섭니다. NVARCHAR / NCHAR는 단순히 "유니코드 컬럼"으로 오해되기 쉽지만, 이기종 이관에서는 문자셋 자체보다 '어디에서 인코딩이 소비되고 있었는가'를 확인하는 것이 핵심입니다. 특정 컬럼만 NVARCHAR2로 정의했다는 것은 그 컬럼에 특별한 용도가 있었다는 뜻입니다.

NVARCHAR2는 단순한 타입 선택이 아니라, 당시 시스템 설계자의 의도가 담긴 흔적입니다. 따라서 이기종 마이그레이션에서 N 타입을 다룰 때는 "타입을 어떻게 변환할 것인가"보다 왜 이 컬럼이 N타입이었는가"를 먼저 이해해야 합니다.

왜 이 컬럼이 N 타입인가

NVARCHAR2로 정의된 컬럼을 발견했다면, 가장 먼저 확인해야 할 것은 이 컬럼이 어떤 이유로 N 타입을 선택했는가 입니다.

DB 내부적으로는 문자 표현 자체를 보장하기 위한 선택입니다. 과거 데이터베이스의 기본 문자셋 제약으로 인해 다국어 데이터를 안정적으로 저장해야 했거나, 한글·다국어 처리를 명시적으로 보장하기 위해 NVARCHAR2가 사용된 경우입니다. 이 유형의 컬럼은 문자 저장을 위한 목적이 분명하며, 이관 시에도 인코딩보다는 길이와 정렬 규칙 정도만 확인하면 되는 경우가 많기 때문에 수월합니다.

그 외의 경우는 DB 외부의 시스템 경계를 맞추기 위한 선택입니다. 외부 시스템과의 전문 통신, 고정 길이 파일 인터페이스, 레거시 메인프레임 연동 등에서 특정 인코딩이나 문자 길이, 공백 패딩을 전제로 한 계약이 존재했고, 이를 충족하기 위한 수단으로 NVARCHAR2가 사용되었을 수 있습니다. 또는 명확한 의도 없이 과거 관행이나 오해로 선택된 경우도 여기에 포함됩니다. 이 유형의 컬럼은 데이터 저장보다 연계와 출력에 의미가 있으므로, 타입 변경은 단순한 스키마 수정이 아니라 시스템 전반의 영향 분석으로 이어져야 합니다.

결국 그 역할이 파악되어야만, NVARCHAR2 → VARCHAR 전환이 안전한지에 대한 판단도 가능해집니다.

외부 연계가 있을 때의 함정

Oracle에서 NVARCHAR2를 사용한 시스템은, 데이터베이스 내부 저장뿐 아니라 애플리케이션, 전문 생성 로직, 연계 미들웨어가 해당 컬럼을 *특정 인코딩(예: UTF-16)* 으로 전제하고 동작하고 있을 가능성이 있습니다. 이런 구조에서 Target DB를 UTF-8 기반의 VARCHAR로 단순 전환하면, 데이터가 외부로 나가는 순간에 문제가 발생할 수 있습니다.

인코딩이 계약의 일부인 경우, 외부 시스템과 데이터를 주고받을 때, 인코딩 방식이 명시적 또는 암묵적 계약이 된 경우에는 상대 시스템에 맞는 N 타입을 설정하여 통신을 했습니다. 하지만 Target을 UTF-8 VARCHAR로 바꾸면, 데이터를 보내기 전에 인코딩 변환이 필요합니다. 변환 로직을 어디에 둘 것인가? 애플리케이션인가, 미들웨어인가, 아니면 DB 레이어인가? 이것은 기술적 선택이 아니라 시스템 아키텍처 재설계가 필요합니다

더 중요한 것은, 상대 시스템이 인코딩 변경을 수용할 수 있는가입니다. 계약서에 명시된 포맷일 수도 있고, 상대 시스템이 레거시라 변경이 불가능할 수도 있습니다.

포맷이 계약의 일부인 경우, 즉 고정 길이 데이터 교환의 경우, 공백 패딩이 포맷의 일부입니다. "각 필드는 정확히 N바이트"라는 약속 아래 NCHAR로 공백을 보장했다면, VARCHAR로 바꾸는 순간 포맷이 깨집니다. 파일 생성 로직을 수정하면 되지만, 문제는 상대 시스템입니다. 협력사가 고정 위치 파싱을 한다면, 1바이트 차이로도 전체 배치가 실패합니다. 파일 포맷 변경을 협력사에 통보하고, 상대 시스템 수정을 기다려야 할 수도 있습니다. 이것은 단순히 SQL에 RPAD()를 추가하는 문제가 아니라, 비즈니스 파트너와의 협의 문제입니다.

EAI, ESB, ETL 같은 중간 계층이 Source 컬럼의 인코딩을 "UTF-16"으로 가정하고 설정되어 있을 수 있습니다. EAI/ESB는 보통 여러 시스템을 연결하고 있습니다. 한 곳의 인코딩 설정을 바꾸면, 연결된 다른 시스템에도 영향이 갈 수 있습니다. 영향도 분석, 관련 팀 협의, 단계적 적용 전략이 모두 필요합니다.

N 타입 컬럼이 전문 생성, 고정 길이 파일, EAI/ESB 연계 등 외부 시스템과 연결되어 있는 경우, 타입 변경은 인코딩 및 출력 포맷 전략 변경으로 확장될 수 있으므로 사전 조사가 필수적입니다


#### 타입 변환은 쉽지만, 맥락 이관은 어렵다

기술적으로 NVARCHAR2VARCHAR로 바꾸는 것은 간단합니다. 대부분의 경우 Source와 Target이 모두 UTF-8이라면 별 문제 없이 이관됩니다. 하지만 왜 그 컬럼이 N타입이었는가를 이해하지 못하면, 외부 연계가 끊기거나, 파일 포맷이 틀어지거나, 인코딩이 깨지는 문제가 오픈 후에 발견됩니다. NVARCHAR2는 단순한 타입이 아니라, 과거 시스템의 운영 맥락이 응축된 설계 결정이기 때문입니다.

따라서 NVARCHAR2/NCHAR 변환은 실무자 인터뷰, 인터페이스 담당자 협의, 외부 시스템 영향도 검토가 모두 필요합니다. 타입을 옮기는 것이 아니라, 과거의 설계 의도를 현재 환경에 맞게 재해석하는 작업입니다.

NVARCHAR2/NCHAR 컬럼을 발견했다면, 다음 질문에 답해야 합니다

  1. 이 컬럼의 값은 시스템을 나가는가?
  1. 나간다면, 인코딩·길이·패딩이 계약의 일부였는가?
  1. 계약이었다면, 변환 로직을 어디에 둘 것인가?

VARCHARCHAR/N 타입의 논의는 길이와 맥락에 관한 것이었습니다. 그러나 문자 데이터가 대용량이 되는 순간, 논의의 축은 바뀝니다. 그때부터 중요한 것은 타입 선택이 아니라, 데이터를 어떻게 저장하고 읽어낼 것인가라는 전략입니다. 다음 글에서는 그 전략 — LOB 변환 — 을 살펴봅니다.

← 목록으로 돌아가기