Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

CSV와 JSON 파일에 스키마 적용하기

CSV나 JSON 파일을 데이터셋의 행과 열로 다루려면 스키마가 필요합니다. UI에서 파일 일부를 보고 스키마를 추론하여 적용하거나, 변환이 실행될 때 Spark가 파일을 읽으며 추론하도록 구성할 수 있습니다.

데이터셋 화면에서 적용

데이터셋의 Apply a schema를 선택하면 데이터 일부를 기준으로 스키마를 추론합니다. 이후 Edit schema에서 열 자료형과 추가 파싱 옵션을 바꿀 수 있습니다.

  • 열 수가 맞지 않는 행을 제거하는 옵션
  • 파일 인코딩
  • 파일 경로, 행의 바이트 오프셋, 가져온 시각과 행 번호를 추가 열로 포함하는 옵션

초기 파일에 맞춰 정적으로 적용한 스키마는 이후 데이터 형식이 바뀌면 맞지 않을 수 있습니다. 입력 형식이 바뀌는 파이프라인에서는 첫 변환 단계에서 동적으로 추론하는 방법을 검토합니다.

동적 추론의 비용과 범위

Spark가 스키마를 추론하려면 입력을 읽어 자료형을 판단해야 하므로 빌드마다 추가 비용이 듭니다. 스키마가 바뀔 가능성이 있을 때 제한적으로 사용하고, 계약이 고정되어 있으면 StructType 또는 DDL 문자열로 스키마를 명시하는 편이 일관성과 성능에 유리합니다.

CSV에서 별도 스키마를 지정하지 않고 inferSchema=False로 읽으면 열을 문자열로 취급합니다. JSON에는 이 CSV 옵션을 그대로 적용하지 않습니다. JSON은 schema가 없으면 입력을 읽어 구조를 추론하므로, 추론을 피하려면 명시적 스키마를 전달합니다.

증분 배치마다 추론 결과가 달라지면 앞서 처리한 데이터와 새 데이터의 자료형이 충돌할 수 있습니다. 증분 파이프라인에서는 입력 스키마를 안정적으로 유지하는 것이 특히 중요합니다.

CSV 변환 예제

다음은 Python Spark Transforms 저장소에서 사용할 수 있는 파일 입력 변환입니다. 경로는 예시이며 실제 원시 CSV 데이터셋과 출력 경로로 바꿉니다. 입력에는 UTF-8 인코딩의 헤더가 있는 CSV 파일이 들어 있다고 가정합니다. 파일 목록 전체를 읽으므로 처리할 파일이 존재해야 합니다.

from transforms.api import Input, Output, transform
from transforms.verbs.dataframes import sanitize_schema_for_parquet


@transform(
    output=Output("/Company/sourceA/parsed/data_csv"),
    raw=Input("/Company/sourceA/raw/data_csv"),
)
def read_csv(ctx, raw, output):
    filesystem = raw.filesystem()
    hadoop_path = filesystem.hadoop_path
    files = [f"{hadoop_path}/{file.path}" for file in filesystem.ls()]
    dataframe = (
        ctx.spark_session.read
        .option("encoding", "UTF-8")
        .option("header", True)
        .option("inferSchema", True)
        .csv(files)
    )
    output.write_dataframe(sanitize_schema_for_parquet(dataframe))

UTF-8은 기본 인코딩이며 예제에서는 명시적으로 지정했습니다. header=True는 첫 행을 열 이름으로 사용합니다. 동적 추론이 필요하지 않으면 inferSchema를 끄거나, 해당 옵션 대신 입력 계약에 맞는 스키마를 reader의 schema 메서드에 전달합니다.

JSON 변환 예제

기본 JSON 입력은 각 줄이 독립적인 JSON 객체인 줄바꿈 구분 형식입니다. 파일 전체가 하나의 여러 줄 JSON 문서라면 multiLine=True로 읽어야 합니다. 다음 예제는 기본 줄바꿈 구분 형식을 사용합니다.

from transforms.api import Input, Output, transform
from transforms.verbs.dataframes import sanitize_schema_for_parquet


@transform(
    output=Output("/Company/sourceA/parsed/data_json"),
    raw=Input("/Company/sourceA/raw/data_json"),
)
def read_json(ctx, raw, output):
    filesystem = raw.filesystem()
    hadoop_path = filesystem.hadoop_path
    files = [f"{hadoop_path}/{file.path}" for file in filesystem.ls()]
    dataframe = (
        ctx.spark_session.read
        .option("multiLine", False)
        .json(files)
    )
    output.write_dataframe(sanitize_schema_for_parquet(dataframe))

multiLine=False는 기본값입니다. JSON 구조가 일정하다면 json 호출 전에 명시적인 스키마를 reader에 설정하여 매번 구조를 추론하지 않도록 할 수 있습니다. 두 예제는 서로 다른 출력 데이터셋을 사용합니다.

Parquet 출력의 열 이름

Transforms의 기본 출력 형식은 Parquet입니다. 자동 추론한 열 이름에 출력에서 허용되지 않는 특수 문자가 포함될 수 있으므로 예제에서는 sanitize_schema_for_parquet를 적용한 뒤 기록합니다. 출력의 열 이름이 소비자가 기대하는 스키마와 맞는지도 관리해야 합니다.

파일 전체나 일부를 SparkSession.read로 읽는 방식은 스키마 추론 외에도 사용할 수 있습니다. 동적 추론이 목적이 아니라면 고정 스키마로 같은 파일 접근 방식을 사용합니다.