Spark는 CREATE TABLE과 TRANSFORM(MAP/REDUCE 포함) 절에서 Hive row format을 지원합니다. 이는 SerDe 클래스나 텍스트 구분자를 지정하는 데 사용되며, Spark와 Hive 생태계 간의 데이터 읽기 및 쓰기 형식을 연결하는 핵심 구문입니다. 본 문서를 통해 ROW FORMAT SERDE와 ROW FORMAT DELIMITED 두 가지 정의 방식과 모든 절 매개변수(필드/컬렉션/맵/행 구분자, NULL 표현, 이스케이프 문자)의 의미와 기본 동작을 이해하게 될 것입니다. 또한 Spark 파서와 Hive 기본 SerDe 속성 간의 매핑 규칙을 파악할 수 있습니다.
개요
Spark에서 Hive row format은 CREATE TABLE 및 TRANSFORM 절에서 사용되며, SerDe 클래스 또는 텍스트 구분자를 지정하는 데 활용됩니다. row_format에는 두 가지 정의 방식이 있습니다:
- SERDE 절: 사용자 정의 SerDe 클래스(정규화된 클래스 이름)를 지정합니다.
- DELIMITED 절: 기본 SerDe에 대한 구분자, 이스케이프 문자, NULL 문자 등을 지정합니다.
구문 분석 구현 관점에서 보면, 이 두 방식은 모두 동일한 SerdeInfo 데이터 구조로 파싱되며, 여기에는 storedAs, formatClasses, serde 클래스 이름 및 serdeProperties 키-값 쌍이 포함됩니다.
구문
row_format:
SERDE serde_class [ WITH SERDEPROPERTIES (k1=v1, k2=v2, ... ) ]
| DELIMITED [ FIELDS TERMINATED BY fields_terminated_char [ ESCAPED BY escaped_char ] ]
[ COLLECTION ITEMS TERMINATED BY collection_items_terminated_char ]
[ MAP KEYS TERMINATED BY map_key_terminated_char ]
[ LINES TERMINATED BY row_terminated_char ]
[ NULL DEFINED AS null_char ]
두 형식은 상호 배타적이므로 둘 중 하나만 선택해야 합니다. DELIMITED 형식에서는 FIELDS TERMINATED BY를 제외한 모든 절이 선택적이며, 필요에 따라 자유롭게 조합할 수 있습니다.
row_format이 사용될 수 있는 구문 위치
row_format은 테이블 생성 문장에서만 사용되는 것이 아닙니다. 다음과 같은 여러 구문 위치에서 row_format을 사용할 수 있습니다:
- CREATE TABLE / CREATE TABLE LIKE: 테이블 생성 시 행 형식을 지정할 수 있습니다.
- CREATE TABLE ... (파티션, 버킷, SKEWED 등 포함):
rowFormat이createFileFormat,locationSpec,bucketSpec등과 함께 테이블 생성 옵션 목록에 나타납니다. - INSERT OVERWRITE (LOCAL) DIRECTORY: 디렉터리에 데이터를 쓸 때
rowFormat과createFileFormat을 지정할 수 있습니다. - TRANSFORM / MAP / REDUCE 절:
SELECT TRANSFORM(...) ... USING script의 입력 행 형식inRowFormat과 출력 행 형식outRowFormat을 지정할 수 있습니다.
파서가 SerdeInfo를 구축하는 방식
AstBuilder.scala에서 visitRowFormat은 구문 트리 노드 유형에 따라 분기합니다:
RowFormatSerdeContext→visitRowFormatSerde:SERDE serde_name를SerdeInfo(serde = Some(name), serdeProperties = ...)로 파싱하며,WITH SERDEPROPERTIES의 키-값 쌍은 직접serdeProperties맵으로 들어갑니다.RowFormatDelimitedContext→visitRowFormatDelimited: DELIMITED 형식의 각 구분자를 해당 이름의 SerDe 속성으로 매핑합니다(아래 4절 참조).
매개변수 설명
| 매개변수 | 의미 | 설명 |
|---|---|---|
| SERDE serde_class | 사용자 정의 SerDe의 정규화된 클래스 이름 지정 | 클래스 로더가 로드할 수 있는 전체 클래스 경로를 제공해야 합니다. 예: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, JsonSerDe 등 |
| SERDEPROPERTIES | SerDe 정의를 위한 키-값 쌍 집합 | SerDe 초기화 속성으로 전달됩니다. 예: ('field.delim'='\t', 'serialization.format'='\t') |
| FIELDS TERMINATED BY | 열 구분자 정의 | 필드 간의 구분 문자를 지정합니다. |
| COLLECTION ITEMS TERMINATED BY | 컬렉션 요소 구분자 정의 | Array, Struct 등 컬렉션 유형 내부 요소 분할에 사용됩니다. |
| MAP KEYS TERMINATED BY | Map 키 구분자 정의 | Map 유형에서 키와 값 사이의 분할에 사용됩니다. |
| LINES TERMINATED BY | 행 구분자 정의 | Spark는 '\n'만 허용하며, 다른 문자는 거부됩니다(아래 참조). |
| NULL DEFINED AS | NULL의 특정 표현 값 정의 | 텍스트 파일에서 NULL 값을 나타내는 문자 시퀀스를 지정합니다. |
| ESCAPED BY | 이스케이프 메커니즘 | 구분자나 특수 문자를 이스케이프하기 위한 문자를 지정합니다. |
참고: SERDEPROPERTIES의 키-값 쌍 작성법은 k=v1이지만, 실제 SQL에서는 일반적으로 인용 부호가 있는 문자열 키-값 쌍으로 작성됩니다(예: WITH SERDEPROPERTIES ('field.delim' = '\t')). 이는 CREATE TABLE과 TRANSFORM 구문 모두에서 지원됩니다.
DELIMITED 절과 기본 SerDe 속성의 매핑(소스 코드 수준)
DELIMITED 형식의 각 절은 독립 필드로 저장되지 않고, Hive SerDe의 serdeProperties로 변환됩니다. visitRowFormatDelimited(AstBuilder.scala 참조)은 다음과 같이 항목별 매핑을 완료합니다:
| SQL 절 | 생성되는 serdeProperties 키 | 설명 |
|---|---|---|
FIELDS TERMINATED BY |
field.delim과 serialization.format |
두 키에 모두 작성되어 다른 SerDe에서 파싱에 사용됩니다. |
ESCAPED BY |
escape.delim |
이스케이프 문자 |
COLLECTION ITEMS TERMINATED BY |
colelction.delim |
오타 주의: colelction('l'이 하나 부족)입니다. 소스 코드 주석에서 이것이 Hive에서 상속된 오타임을 명시하며, 이를 계속 사용해야 합니다. |
MAP KEYS TERMINATED BY |
mapkey.delim |
Map 키-값 구분자 |
LINES TERMINATED BY |
line.delim |
행 구분자 |
NULL DEFINED AS |
— | 소스 코드에서 entry("null", ...)는 아직 구현되지 않았으며, 주석에 TODO we need proper support for the NULL format라고 표시되어 있습니다. 즉, 현재 DELIMITED 형식의 NULL 사용자 정의 값 지원은 여전히 TODO 상태입니다. |
두 가지 주목할 만한 구현 세부 사항:
- LINES TERMINATED BY는
'\n'만 허용:LINES TERMINATED BY의 값이"\n"이 아닌 경우, 파서는 즉시QueryParsingErrors.unsupportedRowFormatLinesTerminatedByError를 발생시킵니다(AstBuilder.scala참조). 이는 Spark에서 행 구분자가 실제로 줄 바꿈 문자로 고정되어 있으며, Hive처럼 자유롭게 행 구분자를 지정할 수 없음을 의미합니다. - 매핑이 곧 SerDe 속성: DELIMITED 형식이 최종적으로 생성하는 것도
SerdeInfo(serdeProperties = entries.toMap)이며, 단지serde클래스를 지정하지 않고 기본(기본) SerDe가 이러한 속성을 읽도록 하는 것입니다.
ROW FORMAT과 STORED AS의 호환성 규칙
ROW FORMAT은 임의의 파일 형식과 자유롭게 조합할 수 없습니다. 파서는 validateRowFormatFileFormat(AstBuilder.scala 참조)에서 다음 조합만 강제로 검증하며, 그 외의 경우에는 ParseException을 발생시킵니다:
ROW FORMAT SERDE ... STORED AS [SEQUENCEFILE | RCFILE | TEXTFILE]— 허용;ROW FORMAT DELIMITED ... STORED AS TEXTFILE— 허용;ROW FORMAT ... STORED AS INPUTFORMAT ... OUTPUTFORMAT ...— 허용(InputFormat/OutputFormat을 명시적으로 지정);- 기타 조합(예:
ROW FORMAT SERDE를 PARQUET/ORC/AVRO 등 자체 SerDe가 있는 형식과 함께 사용하거나,ROW FORMAT DELIMITED를 TEXTFILE이 아닌 형식과 함께 사용) — 허용되지 않음.
관련 테스트 케이스는 DDLParserSuite.scala에 있으며, 예를 들어:
ROW FORMAT SERDE 'customSerde' WITH SERDEPROPERTIES ('prop'='value')를STORED AS otherFormat과 함께 사용하면 오류가 발생:"ROW FORMAT SERDE is incompatible with format 'otherformat', which also specifies a serde";ROW FORMAT DELIMITED FIELDS TERMINATED BY ','를 TEXTFILE이 아닌 형식과 함께 사용하면 오류가 발생:"ROW FORMAT DELIMITED is only compatible with 'textfile', not 'otherformat'".
실제 사용 예제
CREATE TABLE에서 ROW FORMAT SERDE 사용
사용자 정의 SerDe 클래스를 지정하고 SerDe 속성을 전달:
CREATE TABLE custom_serde_example (
user_name STRING,
user_age INT
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
'field.delim' = '|',
'serialization.format' = '|'
);
CREATE TABLE에서 ROW FORMAT DELIMITED 사용
기본 SerDe를 사용하고 전체 구분자 조합 지정:
CREATE TABLE data_delimited_example (
record_id INT,
full_name STRING,
categories ARRAY<STRING>,
properties MAP<STRING, STRING>
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ';' ESCAPED BY '\\'
COLLECTION ITEMS TERMINATED BY '|'
MAP KEYS TERMINATED BY '='
LINES TERMINATED BY '\n'
NULL DEFINED AS 'NULL';
위 예제에서는 필드를 세미콜론으로 분할하고, 배열 요소를 |로 분할하며, Map 키-값 쌍을 =로 분할합니다. 이스케이프 문자는 백슬래시이고, NULL은 텍스트에서 NULL로 표시됩니다. Spark에서 LINES TERMINATED BY는 '\n'만 사용할 수 있습니다.
CREATE TABLE LIKE에서 행 형식 상속
CREATE TABLE LIKE 절을 사용하면 테이블 구조를 복사하면서 행 형식을 명시적으로 덮어쓸 수 있습니다:
CREATE TABLE derived_table
LIKE original_table
ROW FORMAT SERDE 'custom.serialization.Class'
WITH SERDEPROPERTIES ('config' = 'value');
TRANSFORM 절에서 ROW FORMAT 사용
SELECT TRANSFORM(MAP/REDUCE는 별칭)를 사용하면 스크립트의 입력과 출력에 각각 행 형식을 지정할 수 있습니다:
SELECT TRANSFORM (emp_id, emp_name)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
USING '/usr/bin/awk -F\t \'{print $1, toupper($2)}\''
AS (employee_id INT, employee_name STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t';
INSERT OVERWRITE DIRECTORY에서 ROW FORMAT 사용
디렉터리에 텍스트를 내보낼 때 구분자 지정:
INSERT OVERWRITE LOCAL DIRECTORY '/data/output/results'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
SELECT first_name, last_name, department FROM employees;
주의사항 및 제한 사항(소스 코드 기반)
- 행 구분자 제한:
LINES TERMINATED BY는'\n'만 허용하며, 다른 문자를 설정하면 파싱 단계에서 즉시 오류가 발생합니다(AstBuilder.scala소스 코드 참조). colelction.delim오타는 역사적 유산:COLLECTION ITEMS TERMINATED BY가 매핑되는 속성 키는colelction.delim(Hive 오타)입니다.SERDEPROPERTIES를 사용하여 직접 속성을 작성할 때는 이 철자를 유지해야 효과가 있습니다.- NULL 사용자 정의 지원 미완료:
NULL DEFINED AS는 DELIMITED 형식에서 아직 어떤 SerDe 속성에도 매핑되지 않았습니다(소스 코드에 해당 TODO 주석 있음). 실제 효과를 얻으려면 사용되는 SerDe의 자체 속성(예: LazySimpleSerDe의serialization.null.format)과 결합해야 합니다. - SERDE와 자체 SerDe가 있는 파일 형식의 비호환성: PARQUET, ORC 등의 형식은 자체 직렬화 방식을 가지고 있으므로
ROW FORMAT SERDE와 함께 사용할 수 없으며, 그렇게 하면 테이블 생성 시 오류가 발생합니다. - SERDEPROPERTIES는 전달 메커니즘: Spark 자체는 속성 값의 비즈니스 의미를 해석하지 않고, 테이블/디렉터리의 SerDe에 원래 그대로 바인딩하는 역할만 합니다. 실제 파싱은 해당 SerDe 클래스에서 완료됩니다.