본문 바로가기

OpenSource/Spark

[Spark] SparkContext.addFile 과 --files 의 차이점 (Spark on YARN)

Spark 에서 외부데이터를 불러와 처리할 때 코드 상에 SparkContext.addFile 메소드를 호출하거나, spark-submit시 --files 파라미터를 이용하여 외부데이터를 읽어와 처리할 수 있다.
이때 두가지 방법이 서로 동작하는 방식이 달라 YARN에 cluster 모드로 실행시 파일을 읽는 방법이 달라지는데, 이에 대해 정리해보고자 한다.

SparkContext.addFile

공식 문서에 따르면 local file, HDFS 뿐 아니라 HTTP, HTTPS, FTP까지 지정하여 사용할 수 있다고 한다.

//add file 
SparkSession spark = SparkSession.builder().getOrCreate();
spark.sparkContext().addFile("http://web-server.com/data.txt")

이후 YARN에 클러스터 모드로 배포를 진행해 보면 아래와 같은 로그를 확인할 수 있다.

21/06/25 10:21:09 INFO SparkContext: Added file http://web-server.com/data.txt at http://web-server.com/data.txt with timestamp 1624584069834
21/06/25 10:21:11 INFO Utils: Fetching http://web-server.com/data.txt to /yarn/nm/usercache/testuser/appcache/application_1598512664183_1145336/spark-15af8634-15e9-4c67-9927-1b977d8f21c9/userFiles-8833bd9e-3da4-415e-af08-4ee1a756f5cd/fetchFileTemp3381803683049498493.tmp

즉 data.txt 파일이 Driver가 실행된 서버의 appcache에 다운로드 되는것을 확인할 수 있다.

이렇게 디스크에 저장된 파일을 읽으려면

SparkSession spark = SparkSession.builder().getOrCreate();
spark.sparkContext().addFile("http://web-server.com/data.txt");
spark.read().json("file:///" + SparkFiles.get("data.txt")).show(100);

예제와 같이 file:/// prefix를 붙이고, SparkFiles 클래스를 이용하여 파일명만 지정하게 되면 해당 파일이 존재하는 위치의 Path가 출력되어 파일을 읽을 수 있게 된다.

--files

spark-submit -h를 이용하여 --files의 역할에 대해 간단히 확인해보았다.

--files FILES               Comma-separated list of files to be placed in the working
                            directory of each executor. File paths of these files
                            in executors can be accessed via SparkFiles.get(fileName).

Comma(,)로 여러개의 파일을 지정할 수 있으며 각 익스큐터는 SparkFiles.get을 이용하여 파일에 접근할 수 있다고 되어있다.

그런데 실제로 SparkFiles.get을 이용하여 파일에 접근하려면 local 모드로 spark잡이 수행되는 경우에만 가능하고, cluster 모드로 수행되는 경우 SparkFiles.get 을 이용할 수 없다.

아래는 spark-submit 을 통해 YARN에 Cluster 모드로 수행될때 남겨지는 로그이다.

21/06/25 10:21:01 INFO ApplicationMaster: 
===============================================================================
YARN executor launch context:
YARN executor launch context:
  env:
    CLASSPATH -> ...
    SPARK_YARN_STAGING_DIR -> ..
    SPARK_USER -> ..
    ..중략..
    resources:
      data.txt -> resource { scheme: "hdfs" host: "hadoop" port: -1 file: "/user/testuser/.sparkStaging/application_1598512664183_1145677/data.txt" } size: 163292 timestamp: 1624594986993 type: FILE visibility: PRIVATE

로그에서 보는것과 같이 data.txt 파일이 spark잡을 수행하는 유저의 .sparkStaging hdfs에 업로드 되는것이 확인되었다.

즉 SparkContext.addFile로 지정된 파일을 읽을때 로컬디스크에서 읽었기 때문에 file:/// prefix를 붙인 반면, --files 를 이용해 파일을 지정한 경우 hdfs의 경로를 입력해 주면 된다.

## spark submit 실행
spark-submit --master yarn --deploy-mode cluster ..생략.. --files http://web-server.com/data.txt ..생략..

으로 실행했을 때 data.txt를 읽으려면 아래와 같이 처리하면 된다.

SparkSession spark = SparkSession.builder().getOrCreate();

//System.getenv("SPARK_YARN_STAGING_DIR") 를 통해 .sparkStaging 경로를 찾아낼 수 있다.
spark.read().json(System.getenv("SPARK_YARN_STAGING_DIR") + "/data.txt").show(100)

--files 를 이용해 파일을 업로드 하게 되면 hdfs의 .sparkStaging/{Application Id} 경로에 업로드 되게 되는데, 이 경로는 어플리케이션이 수행될때마다 변경이 됨으로 System.getenv("SPARK_YARN_STAGING_DIR") 를 통해 현재 동작중인 어플리케이션의 sparkStaging 경로를 찾아 낸 뒤 파일명과 조합하여 사용하면 된다.

추가내용

Spark에서 addFile 또는 --files로 파일을 추가할 때 파일명은 가장 마지막 '/' 부터 마지막까지 문자열을 파일명으로 하게된다.

http://web-server.com/user-data?age=30 -> user-data?age=30 란 이름으로 저장된다.

이때 파일명에 alias를 주고 싶으면 #원하는파일명 으로 선언하면 된다.

spark-submit .. --files=http://web-server.com/user-data?age=30#user-data.txt