[ETL] 이기종 DBMS 데이터 타입 매핑 표준 정리: PostgreSQL, Iceberg, Spark

1. 개요

여러 업무 시스템의 데이터를 하나의 데이터 플랫폼으로 모으는 파이프라인에서는 데이터 타입 변환이 항상 따라온다. 소스는 PostgreSQL, MySQL, Oracle, SQL Server처럼 제각각이고, 적재 대상은 데이터 마트(PostgreSQL), 데이터 레이크(Iceberg), 처리 엔진(Spark)으로 나뉜다.

같은 정수라도 DBMS마다 이름이 다르다. INT4, MEDIUMINT, NUMBER(9,0), INT가 모두 32비트 정수 계열이다. 이 대응 관계를 파이프라인 코드 곳곳에 흩어 두면 소스가 하나 늘 때마다 변환 로직을 다시 짜게 된다.

이 글에서는 이 문제를 매핑 표 두 장으로 정리한다. 본문의 타입 표는 실제 운영 중인 수집 파이프라인에서 쓰는 기준을 일반화한 예시다.


2. 매핑 체계: 2단계 변환

매핑은 두 단계로 나눈다.

소스 DBMS 타입 → (1단계: DBMS별 정규화) → 표준 타입 → (2단계: 매핑 표) → PostgreSQL / Iceberg / Spark
  • 1단계, DBMS별 정규화: 각 DBMS에서 조회한 타입명을 표준 타입으로 통일한다. 예를 들어 PostgreSQL 카탈로그의 int4는 INTEGER로, MySQL의 TINYINT(1)은 BOOLEAN으로 정규화한다.
  • 2단계, 표준 매핑: 표준 타입을 기준으로 PostgreSQL, Iceberg, Spark 타입을 한 번에 결정한다.

이렇게 나누는 이유는 조합 폭발을 막기 위해서다. 소스 DBMS가 N개, 적재 대상이 M개면 직접 매핑은 N×M 조합이 필요하다. 중간에 표준 타입을 두면 소스가 늘 때 정규화 표 한 장만 추가하면 되고, 2단계 매핑 표는 그대로 재사용한다.


3. 표준 데이터 타입 매핑 표

PostgreSQL 타입을 기준 축으로 두고, Iceberg와 Spark의 대응 타입, 그리고 이 행으로 정규화되는 소스 타입(매핑 전 데이터 타입)을 정리한 표다.

정밀도 표기(n, p, s)가 붙는 타입은 기본형과 동일한 타입으로 매핑한다.

No. PostgreSQL Iceberg Spark 설명 매핑 전 데이터 타입
1 BIGINT long LongType() 64비트 정수 BIGINT, INT64, LONG
2 BIGINT[] list<long> ArrayType(LongType()) 64비트 정수 배열 BIGINT[], LIST(INT64)
3 BIGSERIAL long LongType() 자동 증가 64비트 정수 (BIGINT 기반) BIGSERIAL
4 BIT binary BinaryType() 비트 데이터 타입 BIT
5 BIT(n) binary BinaryType() 비트 데이터 타입 (길이 지정) BIT(n)
6 BOOLEAN boolean BooleanType() TRUE 또는 FALSE 저장 (1비트) BOOLEAN
7 BOOLEAN[] list<boolean> ArrayType(BooleanType()) BOOLEAN 값 배열 BOOLEAN[], LIST(BOOLEAN)
8 BPCHAR string StringType() 고정 길이 문자형 (CHAR과 동일, 내부 표현) BPCHAR
9 BPCHAR(n) string StringType() 고정 길이 문자형 (CHAR과 동일, 내부 표현) BPCHAR(n)
10 BPCHAR[] list<string> ArrayType(StringType()) 고정 길이 문자형 배열 BPCHAR[]
11 BPCHAR(n)[] list<string> ArrayType(StringType()) 고정 길이 문자형 배열 BPCHAR(n)[]
12 BYTEA binary BinaryType() 바이너리 데이터 저장 (이미지, 파일, 암호화된 데이터) BYTEA, BINARY, BLOB, BFILE, FIXED_LEN_BYTE_ARRAY, IMAGE, LONGBLOB, MEDIUMBLOB, RAW(n), TINYBLOB, VARBINARY, LONG RAW
13 BYTEA[] list<binary> ArrayType(BinaryType()) 바이너리 데이터 배열 BYTEA[]
14 CHAR string StringType() 고정 길이 문자 CHAR, CHARACTER, NCHAR
15 CHAR(n) string StringType() 고정 길이 문자 CHAR(n), CHARACTER(n), NCHAR(n)
16 CHAR[] list<string> ArrayType(StringType()) 고정 길이 문자 배열 CHAR[], CHARACTER[]
17 CHAR(n)[] list<string> ArrayType(StringType()) 고정 길이 문자 배열 CHAR(n)[], CHARACTER(n)[]
18 DATE date DateType() 연-월-일 날짜 데이터 DATE, INT32 (DATE)
19 DECIMAL decimal DecimalType() 고정 소수점 숫자 (정확한 소수 연산 가능) DECIMAL
20 DECIMAL(p,0) decimal(p,0) DecimalType(p,0) p자리 정수 (소수점 없음) DECIMAL(p)
21 DECIMAL(p,s) decimal(p,s) DecimalType(p,s) p자리 중 소수 s자리 포함하는 고정 소수점 숫자 DECIMAL(p,s)
22 DOUBLE PRECISION double DoubleType() 64비트 부동소수점 (IEEE 754 표준) DOUBLE PRECISION, BINARY_DOUBLE, DOUBLE, DOUBLE UNSIGNED, FLOAT, FLOAT(63)
23 DOUBLE PRECISION[] list<double> ArrayType(DoubleType()) 64비트 부동소수점 배열 DOUBLE PRECISION[], LIST(DOUBLE)
24 GEOGRAPHY string StringType() 지리 공간 데이터 (PostGIS 확장 필요) GEOGRAPHY
25 GEOMETRY string StringType() 기하학적 공간 데이터 (PostGIS 확장 필요) GEOMETRY
26 INTEGER int IntegerType() 32비트 정수 INTEGER, INT, INT4, INT32, MEDIUMINT
27 INTEGER[] list<int> ArrayType(IntegerType()) 32비트 정수 배열 INTEGER[], LIST(INT32)
28 JSON string StringType() JSON 데이터 (텍스트 기반) JSON
29 JSONB string StringType() JSON 데이터 (이진 저장, 더 빠른 검색) JSONB, MAP, STRUCT
30 NUMERIC decimal DecimalType() 정확한 숫자 저장 (고정 소수점) NUMERIC, FLOAT UNSIGNED
31 NUMERIC(10,0) decimal(10,0) DecimalType(10,0) 10자리 정수 (소수점 없음) INT UNSIGNED, INTEGER UNSIGNED, MEDIUMINT UNSIGNED
32 NUMERIC(10,4) decimal(10,4) DecimalType(10,4) 10자리 중 소수점 4자리 포함 SMALLMONEY
33 NUMERIC(19,4) decimal(19,4) DecimalType(19,4) 화폐 데이터 (19자리, 소수 4자리 포함) MONEY
34 NUMERIC(20,0) decimal(20,0) DecimalType(20,0) 정확한 정수 저장 (최대 20자리) BIGINT UNSIGNED
35 NUMERIC(3,0) decimal(3,0) DecimalType(3,0) 3자리 정수 (소수점 없음) TINYINT UNSIGNED
36 NUMERIC(5,0) decimal(5,0) DecimalType(5,0) 5자리 정수 (소수점 없음) SMALLINT UNSIGNED
37 NUMERIC(p,0) decimal(p,0) DecimalType(p,0) p자리 정수 (소수점 없음) NUMERIC(p)
38 NUMERIC(p,s) decimal(p,s) DecimalType(p,s) p자리 중 소수 s자리 포함하는 고정 소수점 숫자 NUMBER(p,s), NUMERIC(p,s), DECIMAL(p,s) UNSIGNED
39 REAL float FloatType() 32비트 부동소수점 (IEEE 754 표준) REAL, FLOAT(24), BINARY_FLOAT
40 REAL[] list<float> ArrayType(FloatType()) 32비트 부동소수점 배열 REAL[], LIST(FLOAT)
41 SERIAL int IntegerType() 자동 증가 32비트 정수 (INTEGER 기반) SERIAL
42 SMALLINT int ShortType() 16비트 정수 SMALLINT, TINYINT, YEAR
43 SMALLINT[] list<int> ArrayType(ShortType()) 16비트 정수 배열 SMALLINT[]
44 SMALLSERIAL int ShortType() 자동 증가 16비트 정수 (SMALLINT 기반) SMALLSERIAL
45 TEXT string StringType() 무제한 길이 문자열 TEXT, CLOB, DEPRECATED, ENUM, LONGTEXT, MEDIUMTEXT, NCLOB, NTEXT, ROWID, TINYTEXT
46 TEXT[] list<string> ArrayType(StringType()) 문자열 배열 TEXT[], LIST(STRING)
47 TIME time StringType() 시간 데이터 (시간, 분, 초) TIME
48 TIME(s) time StringType() 시간 데이터 (정밀도 포함) TIME(s)
49 TIMESTAMP timestamp TimestampType() 날짜 및 시간 (타임존 없음) TIMESTAMP, DATETIME, DATETIME2, INT64 (TIMESTAMP_MILLIS), INT96, SMALLDATETIME
50 TIMESTAMP(s) timestamp TimestampType() 날짜 및 시간 (타임존 없음, 정밀도 포함) TIMESTAMP(s)
51 TIMESTAMPTZ timestamp TimestampType() 날짜 및 시간 (타임존 포함) TIMESTAMPTZ, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE, DATETIMEOFFSET
52 TIMESTAMPTZ(s) timestamp TimestampType() 날짜 및 시간 (타임존 포함, 정밀도 포함) TIMESTAMPTZ(s)
53 TIMETZ time StringType() 시간 데이터 (타임존 포함) TIMETZ, TIME WITH TIME ZONE
54 TIMETZ(s) time StringType() 시간 데이터 (타임존 포함, 정밀도 포함) TIMETZ(s)
55 UUID string StringType() 고유 식별자 (Universally Unique Identifier) UUID
56 VARCHAR string StringType() 가변 길이 문자열 VARCHAR, VARCHAR2, NVARCHAR, NVARCHAR2, CHARACTER VARYING
57 VARCHAR(n) string StringType() 가변 길이 문자열 VARCHAR(n), VARCHAR2(n), NVARCHAR(n), NVARCHAR2(n), CHARACTER VARYING(n)
58 VARCHAR[] list<string> ArrayType(StringType()) 가변 길이 문자열 배열 VARCHAR[], CHARACTER VARYING[]
59 VARCHAR(n)[] list<string> ArrayType(StringType()) 가변 길이 문자열 배열 VARCHAR(n)[], CHARACTER VARYING[]
60 XML string StringType() XML 데이터 (구조화된 문서 저장) XML, XMLTYPE

4. 타입 계열별 설계 포인트

표의 행 수는 많지만 설계 결정은 몇 가지 계열로 압축된다.

4.1 정수: UNSIGNED는 한 단계 큰 DECIMAL로 승격

PostgreSQL, Iceberg, Spark 모두 부호 없는 정수 타입이 없다. MySQL의 UNSIGNED 계열을 같은 크기의 부호 있는 정수에 담으면 상위 절반 범위가 오버플로된다.

그래서 UNSIGNED는 해당 범위를 통째로 담을 수 있는 고정 소수점 타입으로 승격한다.

소스 (MySQL) 표준 매핑
TINYINT UNSIGNED NUMERIC(3,0)
SMALLINT UNSIGNED NUMERIC(5,0)
INT UNSIGNED, MEDIUMINT UNSIGNED NUMERIC(10,0)
BIGINT UNSIGNED NUMERIC(20,0)

MySQL의 TINYINT(1)은 관례적으로 불리언으로 쓰이므로 정수가 아니라 BOOLEAN으로 정규화한다.

4.2 실수: 정밀도에 따라 REAL과 DOUBLE PRECISION으로 분기

FLOAT라는 이름은 DBMS마다 가리키는 정밀도가 다르다. 정밀도 기준으로 32비트 계열과 64비트 계열을 나눈다.

  • REAL (32비트): FLOAT(24), BINARY_FLOAT (Oracle), SQL Server의 FLOAT(1~24)
  • DOUBLE PRECISION (64비트): FLOAT(63), BINARY_DOUBLE (Oracle), MySQL의 DOUBLE, SQL Server의 FLOAT(>24)

화폐 타입은 부동소수점 오차를 피해야 하므로 고정 소수점으로 보낸다. SQL Server의 MONEY는 NUMERIC(19,4), SMALLMONEY는 NUMERIC(10,4)다.

4.3 문자열: 결국 전부 string으로 수렴

고정 길이(CHAR), 가변 길이(VARCHAR), 무제한(TEXT, CLOB) 모두 Iceberg에서는 string, Spark에서는 StringType() 하나로 수렴한다. 길이 제약은 소스 시스템의 스키마 관심사이고, 분석 플랫폼 쪽에서는 유지할 실익이 없다.

Oracle의 LONG 타입은 Oracle 스스로 사용 중단(deprecated)을 권고하는 타입이라 TEXT 계열로 흡수한다. MySQL의 ENUM도 코드 값 문자열로 취급해 TEXT로 보낸다.

4.4 시간: Spark에 time 타입이 없다

이 매핑 표에서 가장 주의할 지점이다. Iceberg는 time 타입을 지원하지만 Spark에는 시각(time-of-day)만 담는 타입이 없다. 그래서 TIME, TIMETZ 계열은 Iceberg에는 time으로 적재하더라도 Spark 스키마에서는 StringType()으로 다룬다.

타임존 포함 여부와 무관하게 TIMESTAMP, TIMESTAMPTZ는 모두 Spark TimestampType() 하나로 매핑한다. MySQL DATETIME, SQL Server DATETIME2, SMALLDATETIME, Parquet의 INT96도 같은 행으로 정규화된다.

4.5 반정형과 특수 타입: string으로 보존

JSON, JSONB, XML, UUID, 공간 타입(GEOMETRY, GEOGRAPHY)은 모두 문자열로 저장한다. 원본 표현을 그대로 보존하고, 해석은 조회 시점에 맡기는 방식이다.

Parquet의 중첩 타입 MAP, STRUCT도 JSON 문자열로 직렬화해 JSONB 행으로 정규화한다. 바이너리 계열(BLOB, IMAGE, RAW 등)은 전부 BYTEA/binary로 모은다.


5. DBMS별 정규화 표

1단계 정규화에서 쓰는 표다. 분류 열은 각 DBMS에서 식별되는 타입(카탈로그 내부명 또는 판별 조건)이고, 데이터 타입 열이 정규화 결과다. 이 결과 값이 3장 매핑 표의 매핑 전 데이터 타입 열에 대응한다.

5.1 PostgreSQL

PostgreSQL은 카탈로그에서 내부 타입명으로 조회되는 경우가 많다. 배열 타입은 언더스코어 접두사(_int4)로 나타난다.

No. 분류 데이터 타입
1 BOOL BOOLEAN
2 _BOOL BOOLEAN[]
3 BYTEA BYTEA
4 _BYTEA BYTEA[]
5 INT2 SMALLINT
6 SMALLSERIAL SMALLSERIAL
7 _INT2 SMALLINT[]
8 INT4 INTEGER
9 SERIAL SERIAL
10 _INT4 INTEGER[]
11 INT8 BIGINT
12 BIGSERIAL BIGSERIAL
13 _INT8 BIGINT[]
14 FLOAT4 REAL
15 _FLOAT4 REAL[]
16 FLOAT8 DOUBLE PRECISION
17 _FLOAT8 DOUBLE PRECISION[]
18 NUMERIC NUMERIC
19 BPCHAR BPCHAR
20 CHARACTER CHARACTER
21 VARCHAR VARCHAR
22 TEXT TEXT
23 GEOMETRY GEOMETRY (PostGIS 필요)
24 GEOGRAPHY GEOGRAPHY (PostGIS 필요)
25 JSON JSON
26 JSONB JSONB
27 UUID UUID
28 _BPCHAR BPCHAR[]
29 _CHARACTER CHAR[]
30 _CHARACTER CHARACTER[]
31 _VARCHAR VARCHAR[]
32 _VARCHAR CHARACTER VARYING[]
33 _TEXT TEXT[]
34 TIMESTAMP(s) TIMESTAMP
35 TIMESTAMP(s) TIMESTAMP WITH TIME ZONE
36 TIMESTAMPTZ(s) TIMESTAMPTZ
37 TIMESTAMPTZ(s) TIME WITH TIME ZONE
38 TIME(s) TIME
39 TIMETZ(s) TIMETZ
40 DATE DATE

5.2 MySQL / MariaDB

No. 분류 데이터 타입
1 BIT(1) BIT(1)
2 TINYINT(1) BOOLEAN
3 TINYINT TINYINT
4 TINYINT UNSIGNED TINYINT UNSIGNED
5 SMALLINT SMALLINT
6 SMALLINT UNSIGNED SMALLINT UNSIGNED
7 MEDIUMINT MEDIUMINT
8 MEDIUMINT UNSIGNED MEDIUMINT UNSIGNED
9 INT INT
10 INTEGER INTEGER
11 YEAR YEAR
12 INT UNSIGNED INT UNSIGNED
13 INTEGER UNSIGNED INTEGER UNSIGNED
14 BIGINT BIGINT
15 BIGINT UNSIGNED BIGINT UNSIGNED
16 DECIMAL(x,y) DECIMAL(x,y)
17 DECIMAL UNSIGNED DECIMAL(x,y) UNSIGNED
18 FLOAT FLOAT
19 FLOAT UNSIGNED FLOAT UNSIGNED
20 DOUBLE DOUBLE
21 DOUBLE UNSIGNED DOUBLE UNSIGNED
22 CHAR CHAR
23 VARCHAR VARCHAR
24 TINYTEXT TINYTEXT
25 MEDIUMTEXT MEDIUMTEXT
26 TEXT TEXT
27 LONGTEXT LONGTEXT
28 JSON JSON
29 ENUM ENUM
30 DATE DATE
31 TIME(s) TIME
32 DATETIME DATETIME
33 TIMESTAMP(s) TIMESTAMP
34 TINYBLOB TINYBLOB
35 MEDIUMBLOB MEDIUMBLOB
36 BLOB BLOB
37 LONGBLOB LONGBLOB
38 BINARY BINARY
39 VARBINARY VARBINARY
40 BIT(n) BIT(n)
41 GEOMETRY GEOMETRY

5.3 Oracle

Oracle의 NUMBER는 정밀도(precision)와 스케일(scale) 조합으로 실제 크기가 정해진다. 정규화 단계에서 조건 분기로 처리한다.

No. 분류 데이터 타입
1 INTEGER INT
2 INTEGER UNSIGNED INT UNSIGNED
3 FLOAT FLOAT
4 NUMBER(precision <= 9, scale == 0) NUMBER(9,0)
5 NUMBER(9 < precision <= 18, scale == 0) NUMBER(18,0)
6 NUMBER(18 < precision, scale == 0) NUMBER(38,0)
7 NUMBER(scale != 0) NUMBER(p,s)
8 BINARY_DOUBLE BINARY_DOUBLE
9 BINARY_FLOAT BINARY_FLOAT
10 REAL FLOAT
11 CHAR CHAR
12 NCHAR NCHAR
13 VARCHAR VARCHAR2
14 NVARCHAR2 NVARCHAR2
15 VARCHAR2 VARCHAR2
16 LONG DEPRECATED
17 ROWID ROWID
18 NCLOB NCLOB
19 CLOB CLOB
20 XML XMLTYPE
21 DATE DATE
22 TIMESTAMP TIMESTAMP
23 TIMESTAMP WITH LOCAL TIME ZONE TIMESTAMP WITH LOCAL TIME ZONE
24 BLOB BLOB
25 RAW RAW(N)
26 LONG RAW LONG RAW
27 BFILE BFILE

5.4 SQL Server

No. 분류 데이터 타입
1 BIT BIT
2 TINYINT TINYINT
3 SMALLINT SMALLINT
4 INTEGER INT
5 INT INT
6 BIGINT BIGINT
7 NUMERIC(p,s) NUMERIC(p,s)
8 DECIMAL(p,s) DECIMAL(p,s)
9 MONEY MONEY
10 SMALLMONEY SMALLMONEY
11 FLOAT(1~24) REAL
12 REAL REAL
13 DOUBLE FLOAT
14 FLOAT(>24) FLOAT
15 CHAR CHAR
16 NCHAR NCHAR
17 VARCHAR VARCHAR
18 NTEXT NTEXT
19 NVARCHAR NVARCHAR
20 TEXT TEXT
21 XML XML
22 DATE DATE
23 TIME(s) TIME
24 DATETIME(s) DATETIME
25 DATETIME2(s) DATETIME2
26 DATETIMEOFFSET(s) DATETIMEOFFSET
27 SMALLDATETIME SMALLDATETIME
28 BINARY BINARY
29 VARBINARY VARBINARY
30 IMAGE IMAGE

5.5 Parquet

파일 기반 수집(Parquet)도 같은 체계로 정규화한다. Parquet은 논리 타입(logical type)이 물리 타입(physical type) 위에 얹히는 구조라, 분류 열이 논리 타입이고 데이터 타입 열이 물리 타입 기준 표기다.

No. 분류 데이터 타입
1 INT_8 INT32
2 INT_16 INT32
3 DATE INT32 (DATE)
4 TIMESTAMP_MILLIS INT64 (TIMESTAMP_MILLIS)
5 INT64 INT64
6 INT96 INT96
7 BINARY BINARY
8 FLOAT FLOAT
9 DOUBLE DOUBLE
10 BOOLEAN BOOLEAN
11 FIXED_LEN_BYTE_ARRAY FIXED_LEN_BYTE_ARRAY
12 DECIMAL DECIMAL
13 LIST(STRING) LIST(STRING)
14 LIST(BOOLEAN) LIST(BOOLEAN)
15 LIST(TINYINT) LIST(INT32)
16 LIST(SMALLINT) LIST(INT32)
17 LIST(INT) LIST(INT32)
18 LIST(BIGINT) LIST(INT64)
19 LIST(FLOAT) LIST(FLOAT)
20 LIST(DOUBLE) LIST(DOUBLE)
21 Map<K,V> MAP
22 STRUCT STRUCT

6. 정리

  • 이기종 소스의 타입 변환은 DBMS별 정규화(1단계)와 표준 매핑(2단계) 으로 나누면 소스가 늘어도 매핑 표를 재사용할 수 있다.
  • UNSIGNED 정수는 부호 있는 타입에 담기지 않으므로 범위를 커버하는 NUMERIC(p,0)으로 승격한다.
  • FLOAT 계열은 이름이 아니라 정밀도 기준으로 REAL(32비트)과 DOUBLE PRECISION(64비트)을 나눈다.
  • 문자열, 반정형(JSON, XML), UUID, 공간 타입은 string으로 수렴시키고 해석은 조회 시점에 맡긴다.
  • Spark에는 time 타입이 없어 TIME 계열은 Spark에서 StringType() 으로 다룬다. Iceberg에는 time으로 적재할 수 있다.
  • 화폐 타입(MONEY, SMALLMONEY)은 부동소수점이 아니라 고정 소수점(NUMERIC) 으로 보낸다.

Leave a comment