Tools/Spark

Spark SQL과 Hive를 통한 데이터 처리

칼쵸쵸 2024. 3. 26. 23:09

 

Spark와 Hive

스파크 SQL과 Hive의 관계:

  • 스파크 SQL은 Apache Spark의 모듈 중 하나로, SQL과 HiveQL 쿼리를 사용하여 데이터를 처리할 수 있습니다. 이를 통해 사용자는 복잡한 데이터 변환과 분석을 SQL 형태로 간단히 수행할 수 있으며, 내부적으로는 스파크의 RDD(Resilient Distributed Datasets)와 DataFrame API를 사용하여 높은 처리 속도와 효율성을 제공합니다.
  • Hive는 데이터 웨어하우스 인프라를 제공하는 Apache 프로젝트로, SQL과 유사한 HiveQL을 통해 빅데이터를 쿼리, 요약, 분석할 수 있습니다. Hive는 Hadoop 위에서 동작하며, 대용량 데이터셋의 저장과 처리를 위해 설계되었습니다.

https://spark.apache.org/sql/

연동 방법

스파크 SQL은 Hive와 연동할 수 있으며, 이를 통해 Hive 메타스토어에 저장된 테이블의 메타데이터를 읽고, Hive 테이블에서 데이터를 쿼리할 수 있습니다. 스파크와 Hive를 연동하기 위해서는 스파크 세션을 생성할 때 Hive 지원을 활성화해야 합니다.

연동 예제

1. Hive 지원이 활성화된 스파크 세션 생성:

아래는 PySpark에서 SparkSession을 생성하고, Hive와 Kerberos 인증을 설정하는 완전한 예제입니다. 이 예제에서는 Hive 웨어하우스의 위치, Hive 메타스토어 서버의 주소, Kerberos 인증 정보, 그리고 접근해야 할 HDFS 파일 시스템을 설정합니다. 이러한 설정은 PySpark 애플리케이션에서 Hive에 안전하게 접근하고, Kerberos 인증을 사용하여 HDFS 파일 시스템에 접근하기 위해 필요합니다.

from pyspark.sql import SparkSession

# SparkSession 생성
spark = SparkSession.builder 
    .appName("Hive and Kerberos Example")
    # Hive Warehouse 위치 설정
    .config("spark.sql.warehouse.dir", "hdfs://your-hive-warehouse-location")
    # Hive 메타스토어 서버 설정
    .config("hive.metastore.uris", "thrift://your-hive-metastore-server:9083")
    # Kerberos 인증 - 사용자 프린시펄 설정
    .config("spark.yarn.principal", "user@REALM")
    # Kerberos 인증 - 키탭 파일 위치 설정
    .config("spark.yarn.keytab", "/path/to/user.keytab")
    # Kerberos 인증을 통한 HDFS 파일 시스템 접근 설정
    .config("spark.kerberos.access.hadoopFileSystems", "hdfs://your-hdfs-namenode")
    .enableHiveSupport()  # Hive 지원 활성화
    .getOrCreate()

# Spark SQL 구문 실행 예제
# 예를 들어, Hive 테이블에서 데이터 조회
result_df = spark.sql("SELECT * FROM your_hive_table")
result_df.show()

# 사용이 끝난 후, SparkSession 종료
spark.stop()

 

2. Hive 테이블 쿼리하기: 스파크 SQL을 사용하여 Hive 테이블에서 데이터를 쿼리할 수 있습니다. 예를 들어, Hive에 저장된 테이블 my_table에서 데이터를 조회하는 예제는 다음과 같습니다.

 

spark.sql("SELECT * FROM my_table").show()

 

 

3. Hive 테이블에 데이터 쓰기: 스파크 DataFrame을 사용하여 Hive 테이블에 데이터를 쓸 수도 있습니다.

val myDataFrame = spark.read.json("path/to/jsonfile")
myDataFrame.write.mode("overwrite").saveAsTable("my_table")

 

주의사항

  • 스파크와 Hive를 연동하기 위해서는 스파크에 Hive 관련 설정이 올바르게 구성되어 있어야 합니다. 이에 필요한 설정은 스파크 설치 디렉터리의 conf 폴더 안에 있는 hive-site.xml 파일을 통해 관리할 수 있습니다.
  • Hive 메타스토어와 호환되는 버전의 스파크를 사용해야 합니다. 때때로 버전 호환성 문제로 인해 연동에 실패할 수 있으니, 사용하는 스파크와 Hive의 버전이 호환되는지 확인하는 것이 중요합니다.

 

DataFrame과 Spark SQL을 통한 Hive 테이블 데이터 처리

SQL 쿼리를 사용한 데이터 처리

  • 가독성: 복잡한 데이터 변환 작업이나 집계를 수행할 때, SQL 쿼리가 더 읽기 쉽고 이해하기 쉬울 수 있습니다. SQL에 익숙한 사용자에게는 더 자연스러운 선택일 수 있습니다.
  • 호환성: 기존 SQL 쿼리를 Spark로 마이그레이션하는 경우, SQL을 직접 사용하는 것이 편리할 수 있습니다.
  • 유니폼 인터페이스: 데이터 소스가 무엇이든 간에 동일한 SQL 쿼리를 사용할 수 있습니다.
from pyspark.sql import SparkSession

# Hive 지원을 포함한 Spark 세션 초기화
spark = SparkSession.builder \
    .appName("Hive Example") \
    .enableHiveSupport() \
    .getOrCreate()

# Hive 테이블을 조회하는 SQL 쿼리 실행
df1 = spark.sql("SELECT * FROM my_hive_table WHERE some_column > 100")

 

DataFrame API를 사용한 데이터 처리

  • 프로그래밍 언어의 통합: DataFrame API는 Scala, Java, Python, R 등 여러 프로그래밍 언어에서 사용할 수 있으며, 각 언어의 특성을 활용할 수 있습니다. 예를 들어, Scala에서는 타입 안전성을, Python에서는 동적 타이핑과 라이브러리 생태계를 활용할 수 있습니다.
  • 코드 자동 완성 및 타입 체크: 대부분의 IDE에서 DataFrame API 사용 시 자동 완성 기능을 제공하며, 컴파일 타임에 타입 체크를 할 수 있어 오류를 사전에 방지할 수 있습니다.
  • 함수형 프로그래밍과 유연성: DataFrame API를 사용하면 함수형 프로그래밍 패러다임을 적용하여 데이터 처리 파이프라인을 구성할 수 있습니다. 또한, 런타임에 동적으로 변환 작업을 구성하는 것이 더 용이할 수 있습니다.
from pyspark.sql import SparkSession

# Hive 지원을 포함하여 Spark 세션 초기화
spark = SparkSession.builder \
    .appName("DataFrame Hive Example") \
    .enableHiveSupport() \
    .getOrCreate()

# Hive 테이블을 DataFrame으로 로드
hive_df = spark.table("my_hive_table")

# DataFrame API를 사용하여 데이터 필터링
filtered_df = hive_df.filter(hive_df["some_column"] > 100)

# 필터링된 데이터 보기
filtered_df.show()

 

성능

  • Spark 2.x 버전부터는 DataFrame과 Dataset API를 사용한 작업이 Catalyst 옵티마이저와 Tungsten 실행 엔진을 통해 내부적으로 최적화되므로, SQL 쿼리를 사용한 경우와 비교해 성능상의 큰 차이가 없습니다. 실제로, SQL 쿼리든 DataFrame API를 사용하든, Spark는 내부적으로 같은 논리적 실행 계획으로 변환합니다.
  • 성능은 주로 작업의 특성, 데이터의 크기와 구조, 사용한 클러스터의 구성 등에 따라 달라질 수 있으며, SQL 쿼리와 DataFrame API 선택 사이에서의 성능 차이보다는 다른 요소들이 더 중요할 수 있습니다.

 

Temp View와 하이브 테이블 저장

createOrReplaceTempView 메서드를 사용하면 Spark의 DataFrame을 Temp View(또는 임시 테이블)로 등록할 수 있습니다.

이렇게 하면 Spark SQL을 사용하여 SQL 쿼리를 실행할 때, 마치 실제 데이터베이스 테이블처럼 해당 뷰를 참조할 수 있게 됩니다.

하지만 이 Temp View는 Spark 세션 내에서만 존재하고, 세션이 종료되면 사라집니다.

따라서, createOrReplaceTempView를 통해 생성된 뷰는 Spark 세션의 생명주기와 바인딩되어 있으며, 하이브(Hive)나 다른 외부 데이터베이스에는 저장되지 않습니다.

예를 들어, 다음과 같은 코드로 DataFrame을 Temp View로 등록할 수 있습니다:

val df: DataFrame = ...
df.createOrReplaceTempView("my_temp_view")

val result = spark.sql("SELECT * FROM my_temp_view")

 

여기서 my_temp_view는 SQL 쿼리 내에서 참조할 수 있는 Temp View의 이름입니다.

이 뷰는 Spark SQL 쿼리에서 사용할 수 있지만, Spark 세션이 종료되면 뷰도 함께 사라지기 때문에 영구적으로 데이터를 저장하고 싶다면 다른 방법을 사용해야 합니다.

만약 데이터를 하이브(Hive)에 저장하고자 한다면, Spark에서는 DataFrame을 하이브 테이블로 저장할 수 있는 방법을 제공합니다. 이를 위해선 Spark 세션을 하이브와 통합해야 하며, 이후에는 saveAsTable 메서드를 사용하여 DataFrame을 하이브 테이블로 저장할 수 있습니다. 

df.write.mode("overwrite").saveAsTable("hive_table_name")

 

이 방식을 사용하면, DataFrame의 데이터가 하이브 테이블 hive_table_name으로 저장되며, 이 테이블은 Spark 세션 외부에서도 접근할 수 있고, 다른 하이브 클라이언트를 통해서도 조회할 수 있습니다.

 

 

Temp View 생성 메서드

1. createTempView 메서드

createTempView 메서드는 지정된 이름으로 새로운 Temp View를 생성합니다. 만약 같은 이름의 뷰가 이미 존재한다면, 이 메서드는 오류를 발생시킵니다.

# DataFrame 생성 예시
df = spark.createDataFrame([(1, "foo"), (2, "bar")], ["id", "value"])

# Temp View 생성
df.createTempView("temp_view")

# SQL 쿼리를 사용해 Temp View 조회
spark.sql("SELECT * FROM temp_view").show()

 

2. createOrReplaceTempView 메서드

createOrReplaceTempView 메서드는 지정된 이름으로 Temp View를 생성합니다. 만약 같은 이름으로 이미 뷰가 존재한다면, 기존 뷰를 새로운 DataFrame으로 대체합니다. 이 방법은 개발 과정에서 데이터 스키마가 변경되었을 때 유용하게 사용할 수 있습니다.

 

# DataFrame 업데이트 예시
df2 = spark.createDataFrame([(1, "baz")], ["id", "value"])

# 기존 뷰를 새로운 DataFrame으로 대체
df2.createOrReplaceTempView("temp_view")

# 변경된 뷰 조회
spark.sql("SELECT * FROM temp_view").show()

 

3. createGlobalTempView 메서드

createGlobalTempView 메서드는 스파크 세션 간에 공유될 수 있는 global Temp View를 생성합니다. 이 뷰는 global_temp 데이터베이스 내에서 관리됩니다. 만약 같은 이름의 글로벌 뷰가 이미 존재한다면, 이 메서드는 오류를 발생시킵니다.

 

df.createGlobalTempView("global_temp_view")

# global Temp View 조회
spark.sql("SELECT * FROM global_temp.global_temp_view").show()

 

4. createOrReplaceGlobalTempView 메서드

createOrReplaceGlobalTempView 메서드는 지정된 이름으로 global Temp View를 생성하거나, 같은 이름으로 존재하는 글로벌 뷰를 대체합니다. 이 메서드는 여러 Spark 세션 간에 데이터를 공유해야 할 때 유용합니다.

df2.createOrReplaceGlobalTempView("global_temp_view")

# 변경된 global Temp View 조회
spark.sql("SELECT * FROM global_temp.global_temp_view").show()

 

Temp View 생성 시점

Temp View를 생성하는 시점은 주로 다음과 같은 경우입니다:

  • 복잡한 데이터 처리 과정을 단계별로 나누고 싶을 때: 복잡한 데이터 변환 작업을 여러 단계로 나눠서 수행해야 할 때, 각 단계의 결과를 Temp View로 생성하여 관리할 수 있습니다.
  • SQL 쿼리를 통해 데이터에 접근하고 싶을 때: DataFrame으로부터 생성된 Temp View는 SQL 쿼리를 통해 쉽게 접근하고 질의할 수 있습니다. 특히, SQL에 익숙한 사용자나 다른 시스템과의 호환성을 위해 유용합니다.
  • 여러 데이터 프레임간의 관계를 명확히 하고자 할 때: 서로 관련 있는 여러 데이터 프레임이 있을 때, 각각을 Temp View로 생성하고 SQL 쿼리를 통해 이들 간의 관계를 쉽게 탐색하고 분석할 수 있습니다. 이 방법은 데이터 간의 조인이나 복잡한 집계를 수행할 때 특히 유용합니다.
  • 데이터를 임시로 저장하고 싶을 때: 계산 비용이 높은 데이터 처리 작업의 결과를 Temp View로 저장함으로써, 같은 세션 내에서 이후의 쿼리나 데이터 처리 작업에서 재사용할 수 있습니다. 이는 성능 최적화에 도움을 줄 수 있습니다.
  • global Temp View를 사용하여 여러 세션 간 데이터 공유가 필요할 때: 여러 Spark 세션 간에 일관된 데이터 뷰가 필요한 경우, global Temp View를 사용하여 데이터를 공유하고, 이를 통해 여러 사용자나 작업이 동일한 데이터 뷰를 참조할 수 있게 할 수 있습니다.

Temp View  vs Global Temp View

  • Temp View: 한 Spark 세션 내에서만 접근 가능하며, 해당 세션이 종료되면 사라집니다. 데이터 탐색이나 단일 사용자 작업에 적합합니다.
from pyspark.sql import SparkSession

# SparkSession 생성
spark = SparkSession.builder.appName("TempViewExample").getOrCreate()

# 데이터프레임 생성
data = [("Alice", 1), ("Bob", 2)]
columns = ["Name", "ID"]
df = spark.createDataFrame(data, schema=columns)

# Temp View 생성
df.createOrReplaceTempView("people_temp_view")

# Temp View 조회
spark.sql("SELECT * FROM people_temp_view").show()

# SparkSession 종료
spark.stop()

# 새로운 SparkSession 생성
spark_new = SparkSession.builder.appName("NewSession").getOrCreate()

# 같은 뷰 이름으로 쿼리 시도 - 실패할 것임
try:
    spark_new.sql("SELECT * FROM people_temp_view").show()
except Exception as e:
    print("Error:", e)

# 두 번째 SparkSession 종료
spark_new.stop()
  • Global Temp View: 여러 Spark 세션 간에 공유될 수 있으나, 모든 global Temp View는 global_temp라는 데이터베이스 네임스페이스 아래에 생성됩니다. 이 뷰들은 Spark 애플리케이션이 종료될 때까지 유지되며, 분산 환경에서의 데이터 공유에 적합합니다.
from pyspark.sql import SparkSession

# SparkSession 생성
spark = SparkSession.builder.appName("GlobalTempViewExample").getOrCreate()

# 데이터프레임 생성
data = [("Charlie", 3), ("David", 4)]
columns = ["Name", "ID"]
df = spark.createDataFrame(data, schema=columns)

# global Temp View 생성
df.createOrReplaceGlobalTempView("people_global_temp_view")

# global Temp View 조회
spark.sql("SELECT * FROM global_temp.people_global_temp_view").show()

# SparkSession 종료
spark.stop()

# 새로운 SparkSession 생성
spark_new = SparkSession.builder.appName("NewSessionForGlobalView").getOrCreate()

# 같은 global Temp View 이름으로 쿼리 - 성공할 것임
spark_new.sql("SELECT * FROM global_temp.people_global_temp_view").show()

# 두 번째 SparkSession 종료
spark_new.stop()

이 예시에서는, 애플리케이션이 종료되기 전까지 global_temp.people_global_temp_view (global Temp View)에 여러 SparkSession에서 접근할 수 있음을 보여줍니다.

이처럼 Temp View는 세션-스코프 레벨에서만 존재하는 반면, global Temp View는 애플리케이션 전역에서 사용할 수 있는 점에서 차이가 있습니다.

 

Temp View를 통한 DataFrame API와 SPARK SQL 혼합 사용

Temp View를 만드는 것은 특정 DataFrame에 대한 SQL 쿼리를 실행하고 싶을 때 유용합니다. 이 방법은 프로그래밍적인 접근과 SQL 쿼리의 장점을 결합하고자 할 때, 특히 적합합니다.

 

1. 복잡한 데이터 변환 작업

DataFrame API만을 사용하여 처리하기 복잡한 데이터 변환 작업이 있을 때, SQL 쿼리를 사용하면 보다 직관적이고 이해하기 쉬운 코드를 작성할 수 있습니다. 이 경우, DataFrame을 Temp View로 만들고 SQL 쿼리를 실행하는 것이 좋습니다.

 

2. SQL에 익숙한 팀 또는 개발자

팀 또는 개발자가 SQL에 더 익숙하고, SQL을 통해 데이터를 빠르게 탐색하고 싶어할 때, Temp View를 사용하면 기존의 SQL 지식을 활용하여 Spark 데이터를 쉽게 다룰 수 있습니다.

 

3. 인터랙티브 데이터 탐색

Temp View는 인터랙티브한 데이터 탐색과 분석에도 유용합니다. Jupyter Notebook이나 Databricks 노트북과 같은 환경에서는 DataFrame을 Temp View로 만든 후, 여러 SQL 쿼리를 빠르게 실행하여 데이터를 탐색할 수 있습니다.

 

4. 다양한 데이터 소스 간의 조인

여러 데이터 소스에서 로드한 DataFrame들을 조인해야 할 때, 이들 각각을 Temp View로 만들고 SQL 쿼리를 사용하여 조인할 수 있습니다. 이는 코드를 간결하게 유지하면서도 다양한 데이터 소스를 효율적으로 결합할 수 있는 방법입니다.

 

5. 중간 결과의 재사용

특정 DataFrame에 대한 작업을 여러 번 수행해야 할 때, 그 결과를 Temp View로 만들면 중간 결과를 쉽게 재사용할 수 있습니다. 이는 복잡한 데이터 파이프라인에서 중간 단계의 데이터를 다른 쿼리에서 다시 활용하고 싶을 때 유리합니다.

 

결론

Temp View를 사용하는 것은 SQL 쿼리의 강력함을 활용하고자 할 때, 특히 좋습니다. 프로그래밍적 접근과 SQL 기반 접근 사이의 가교 역할을 하며, 데이터 처리 작업의 유연성을 크게 높여줍니다. 그러나 사용 상황은 프로젝트의 요구 사항, 개발자의 선호도, 그리고 작업의 복잡성에 따라 달라질 수 있습니다.

 

각 시스템별 데이터 저장 구조와 특징

기능 Spark SQL Hive SQL 관계형 데이터베이스
데이터 저장 위치 분산 파일 시스템 (예: HDFS, S3) 분산 파일 시스템 (예: HDFS) 중앙집중식 서버
데이터 처리 메모리 내 계산, 분산 처리 주로 디스크 기반 처리, 분산 처리 디스크 기반 처리, 중앙 처리
스키마 처리 스키마 온 리드 스키마 온 리드 스키마 온 라이트
확장성 수평적 확장성이 뛰어남 수평적 확장성이 뛰어남 수직적 확장에 주로 의존
데이터 무결성과 트랜잭션 제한적인 트랜잭션 지원 제한적인 트랜잭션 지원 강력한 트랜잭션, ACID 지원
쿼리 성능 매우 빠름, 메모리 최적화 느림 (MapReduce 기반), 최근 버전에서 개선 일반적으로 빠름
사용 사례 실시간 처리, 대규모 데이터셋 처리 배치 처리, 대규모 데이터 웨어하우스 온라인 트랜잭션 처리, 복잡한 쿼리

 

 

예시

Spark SQL 작업을 시작하기 위해서는 먼저 SparkSession 객체를 생성해야 합니다. 이 객체는 Spark의 모든 기능을 초기화하고 사용자가 SQL 작업을 수행할 수 있도록 합니다.

    import org.apache.spark.sql.SparkSession

    // SparkSession 초기화
    val spark = SparkSession.builder.appName("DataBase Example").config(conf).getOrCreate()

    // 암시적 변환을 위한 import
    import spark.implicits._

    val data = Seq(
      ("James", 34),
      ("Anna", 28),
      ("Robert", 23)
    )

    val df = data.toDF("name", "age")
    spark.sql("CREATE DATABASE IF NOT EXISTS example_db")
    spark.sql("USE example_db")
    // 테이블 저장 위치와 포맷 설정
    val warehouseLocation = "/Users/user/IdeaProjects/jpl-spark-scala/warehouse/example_db"


    df.write
      .mode("overwrite")
      .option("path", warehouseLocation)
      .saveAsTable("people")
  }

 

이 스크립트는 스칼라를 사용하여 Spark SQL을 통해 데이터베이스를 생성하고, 코드로 생성된 데이터를 DataFrame으로 만든 후, 이를 테이블로 저장하는 과정을 보여줍니다. 테이블은 사용자가 지정한 경로에 저장됩니다. 이 경로는 Spark의 설정을 통해 관리되며, 이 예제에서는 Spark의 웨어하우스 디렉토리 경로를 사용하였습니다.

 

결과

 

 

1. _SUCCESS

이 파일은 Spark 작업이 성공적으로 완료되었음을 나타냅니다. Spark 작업이 모든 파티션을 성공적으로 처리하고 저장했을 때 생성됩니다. 파일 자체는 비어 있으며, 그 존재 자체로 작업의 성공을 표시합니다.

2. _SUCCESS.crc

이것은 _SUCCESS 파일의 CRC (Cyclic Redundancy Check) 체크섬을 저장하는 파일입니다. 이는 파일 무결성을 검증하는 데 사용됩니다.

3. part-00000-...

이 파일들은 실제 데이터를 포함하고 있습니다. part-로 시작하는 파일은 데이터가 파티셔닝 되어 저장되었음을 나타냅니다. 파일 이름 뒤에 붙은 긴 해시 값은 각 파일이 고유하게 식별되도록 하며, 여러 작업 또는 노드 간 충돌을 방지합니다.

 

4. 분산 환경에서의 파일 생성

만약 Spark 클러스터가 여러 노드로 구성되어 있다면, 데이터는 여러 노드에 걸쳐 분산되어 처리되고 저장됩니다. 각 노드는 자신의 파티션을 처리하고, 결과는 일반적으로 HDFS(Hadoop Distributed File System)와 같은 분산 파일 시스템에 저장됩니다. 이 때, 각 노드에서 처리된 데이터는 part- 파일로 각각 저장되며, 파일 이름은 각 작업과 노드에 따라 유니크한 ID를 가집니다.

각 파티션 파일(part-00000-... 등)은 해당 노드에서 처리된 데이터의 일부를 포함하고 있으며, 여러 파일이 합쳐져 전체 데이터셋을 구성합니다. _SUCCESS 파일은 모든 노드가 자신의 작업을 성공적으로 완료했음을 나타내는 전체 작업의 성공 마커로, 최종적으로 생성됩니다.

이렇게 분산 환경에서의 데이터 처리와 저장은 데이터의 크기와 복잡성에 따라 확장성과 병렬 처리의 이점을 제공합니다. 각 노드는 독립적으로 작업을 수행하고, 최종 결과는 분산 파일 시스템에 통합되어 접근할 수 있습니다.

 

5. example_db.db 디렉토리

 

.db로 끝나는 디렉토리는 Spark SQL에서 데이터베이스를 관리하는 데 사용됩니다. 이 디렉토리는 해당 데이터베이스와 연관된 메타데이터를 저장하는 용도로 사용되곤 합니다. 일반적으로, 데이터베이스에 대한 구조적 정보나 스키마 정보를 포함할 수 있지만, 실제 데이터 자체는 다른 part-xxxxx 파일들에 저장됩니다.

그러나, 스파크 설정이나 데이터베이스 설정에 따라 이 디렉토리가 비어 있을 수도 있습니다. Spark에서 데이터베이스를 만들면, 해당 데이터베이스에 대한 정보를 저장하기 위한 디렉토리가 생성되지만, 실제로 데이터베이스에 테이블을 추가하지 않았거나 특정 설정이 없는 경우 내용물이 없을 수 있습니다.

이 경우, .db 디렉토리는 주로 다음과 같은 목적으로 사용됩니다:

  1. 데이터베이스 메타데이터 저장: 데이터베이스 레벨의 설정이나 스키마 정보를 포함할 수 있습니다.
  2. 네임스페이스 구분: 여러 데이터베이스가 각각의 고유한 디렉토리를 갖게 함으로써 관리와 구분이 용이해집니다.

이 디렉토리가 비어 있다면, 현재 데이터베이스에 실제 테이블이 저장되지 않았거나, 테이블 생성 시 메타데이터를 별도로 저장하는 설정이 활성화되지 않았을 가능성이 있습니다. 또한, 테이블이 다른 경로에 저장되도록 설정되었을 수도 있습니다. 데이터베이스와 테이블을 관리할 때는 Spark의 설정 및 데이터베이스 구성을 확인하는 것이 중요합니다.