Databricksにて外部Hive Metastoreを構築する方法の検証

## 注意事項
調査中ですが、意見を募集したいため記事を投稿します。
記事内容を更新する可能性があります。

## 概要

Databricksにて外部Hive Metastoreを構築する方法の検証を整理します。
下記の表が外部Hive Metastoreを構築する方法の概要、それぞれの検証結果の詳細を後述します。

| #    | 方法                                                         | 実施可否 | 問題・課題                                  
| ---- | ------------------------------------------------------------ | -------- | -------------------------------------------------- |
| 1    | Databricksのドキュメント通りにSpark Configにより構築する方法 | 〇       | spark.conf.getにより接続情報を取得できてしまうこと。   |
| 2    | セッションスコープでSpark Configを変更 | ×        | 設定を変更できないこと。 |
| 3    | Hive Metastoreへの接続情報ファイル（hive-site.xml）を置き換える方法 | ×        | ファイル置き換え前のデータベースに接続してしまうこと。 |
| 4    | Hive Metastoreへの接続情報ファイルに対する参照パスの環境変数（CONF_HIVE_CONF_DIR）を置き換える方法 | ×        | 外部メタストアデータベースへ接続できず、データベースを作成しても永続化されないこと。             |

Databricksではデフォルトで内部のHive Metastoreデータベースを参照する仕様であり、Azure Databricksではリージョンごとに共通のAzure Database for Mysqlを利用するようになっているようです。Hive Metastoreデータベースへの接続情報の取得方法としては下記のドキュメントで紹介されております。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/ac6b7d47-7676-c204-4e1a-09dda7b56d41.png)
引用元：[Azure Purview で Hive メタストア データベースを登録してスキャンを設定する - Azure Purview | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/purview/register-scan-hive-metastore-source#creating-and-running-a-scan)

参考になる情報としてDatabricksのドキュメントがありますが、AWS版DatabricksとAzure Databricksでは記載内容が異なり、どちらも参考になります。

-   [外部Apache Hiveメタストア - Azure Databricks - Workspace | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/databricks/data/metastores/external-hive-metastore)
-   [External Apache Hive metastore | Databricks on AWS](https://docs.databricks.com/data/metastores/external-hive-metastore.html)

SparkにおけるHive Metastoreの動作を概要を利用するには、GMOインターネットグループ様のブログ記事がとても参考になります。

- [Hadoop上でSpark＋Delta Lakeを検証してみた～続き | GMOインターネットグループ 次世代システム研究室](https://recruit.gmo.jp/engineer/jisedai/blog/spark-and-deltalake-on-hadoop-continue/#anchor12)

## 1. Databricksのドキュメント通りにSpark Configにより構築する方法
### 構築方法
下記の記事に記載しております。

-   [Databricksにおける外部Hive MetastoreをMySQL（Azure Database for MySQL）にてローカルモードでSpark Configにて構築する方法 - Qiita](https://qiita.com/manabian/items/21dc7f6a3e694cff2854)
-   [Databricksにおける外部Hive MetastoreをAzure SQL DatabaseにてローカルモードでSpark Configにて構築する方法 - Qiita](https://qiita.com/manabian/items/f9509b5fccb8513b2d90)

### 課題
#### 課題1-1. spark.conf.getにより接続情報を取得できてしまうこと
Databricksシークレットに格納することで値を表示しないようにできてしまうが、jdbc経由でHive Metastoreデータベースに接続できてしまうという課題がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/fc6d8514-63be-7972-0041-ff37d2f8fe66.png)


## 2. セッションスコープでSpark Configを変更
### 構築方法
Spark Cofigをセッション中に変更

```python
spark.conf.set("spark.hadoop.javax.jdo.option.ConnectionDriverName", "<driver>")
spark.conf.set("spark.hadoop.javax.jdo.option.ConnectionURL","<url>")
spark.conf.set("spark.hadoop.javax.jdo.option.ConnectionUserName","<usernaem>)
spark.conf.set("spark.hadoop.javax.jdo.option.ConnectionPassword","<password>")
```

#### 課題2-1. Spark Configの設定を変更できないこと。
`spark.conf.isModifiable`により変更可否を確認できるが、いずれの設定も`False`である。

```python
hive_driver = spark.conf.isModifiable("spark.hadoop.javax.jdo.option.ConnectionDriverName")
hive_url = spark.conf.isModifiable("spark.hadoop.javax.jdo.option.ConnectionURL")
hive_username = spark.conf.isModifiable("spark.hadoop.javax.jdo.option.ConnectionUserName")
hive_password = spark.conf.isModifiable("spark.hadoop.javax.jdo.option.ConnectionPassword")

print(f"ConnectionDriverNameの設定変更の可否：{hive_driver}")
print(f"ConnectionURLの設定変更の可否       ：{hive_url}")
print(f"ConnectionUserNameの設定変更の可否  ：{hive_username}")
print(f"ConnectionPasswordの設定変更の可否  ：{hive_password}")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/3ca3acb1-64d2-e473-167a-c3e032a07ea8.png)

参考情報

- [Notebook で spark プロパティが変更可能かどうかを確認する方法-Azure Databricks - Workspace | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/databricks/kb/notebooks/check-spark-property-modifiable)

## 3. Hive Metastoreへの接続情報ファイル（hive-site.xml）を置き換える方法
### 構築方法
下記のコードを実行してhive-site.xmlを置き換えます。

```python
contents = """

<configuration>
  
<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:sqlserver://{databaseへの接続情報}</value>
  <description>JDBC connect string for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
  <description>Driver class name for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>{ユーザー}</value>
  <description>Username to use against metastore database</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>{パスワード}</value>
  <description>Password to use against metastore database</description>
</property>
<!-- If the following two properties are not set correctly, the metastore will
     attempt to initialize its schema upon startup
-->
<property>
  <name>datanucleus.schema.autoCreateAll</name>
  <value>false</value>
</property>
<property>
  <name>datanucleus.autoCreateSchema</name>
  <value>false</value>
</property>
<property>
  <name>datanucleus.fixedDatastore</name>
    <value>true</value>
</property>
<property>
  <name>datanucleus.connectionPool.minPoolSize</name>
    <value>0</value>
</property>
<property>
  <name>datanucleus.connectionPool.initialPoolSize</name>
    <value>0</value>
</property>
<property>
  <name>datanucleus.connectionPool.maxPoolSize</name>
    <value>1</value>
</property>
<property>
  <name>hive.stats.autogather</name>
  <value>false</value>
</property>
       
  <property>
    <name>mapred.reduce.tasks</name>
    <value>100</value>
  </property>
  <!-- To mitigate the problem of PROD-4498 and per HIVE-7140, we need to bump the timeout.
       Since the default value of this property used by Impala is 3600 seconds, we will use this value for
       actual deployment
       (http://www.cloudera.com/content/cloudera/en/documentation/core/latest/topics/cm_props_cdh530_impala.html).
  -->
  <property>
    <name>hive.metastore.client.socket.timeout</name>
    <value>3600</value>
  </property>
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/local_disk0/tmp</value>
  </property>

  <property>
    <name>hive.metastore.client.connect.retry.delay</name>
    <value>10</value>
  </property>
  <property>
    <name>hive.metastore.failure.retries</name>
    <value>30</value>
  </property>


</configuration>
     
"""

dbutils.fs.put(
    file = "file:/databricks/hive/conf/hive-site.xml",
    contents = contents,
    overwrite = True,
)
```

### 課題
#### 課題3-1. ファイル置き換え前のデータベースに接続してしまうこと
ファイルを置き換えてもデータベースは切り替わらず、クラスターを再起動すると`hive-site.xml`が初期化されてしまいます。

`hive-site.xml`置き換え前のデータベース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/0d45310c-5e7a-28bc-f87b-68d9d23441ff.png)

`hive-site.xml`置き換え後のデータベース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/d306fbc6-e956-dd44-4377-73a8308ea292.png)


## 4. Hive Metastoreへの接続情報ファイルに対する参照パスの環境変数（CONF_HIVE_CONF_DIR）を置き換える方法
### 構築方法

下記のコードにより、dbfs上に`hive-site.xml`を配置します。。

```python
contents = """

<configuration>
  
<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:sqlserver://{databaseへの接続情報}</value>
  <description>JDBC connect string for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
  <description>Driver class name for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>{ユーザーID}</value>
  <description>Username to use against metastore database</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>{パスワード}</value>
  <description>Password to use against metastore database</description>
</property>
<!-- If the following two properties are not set correctly, the metastore will
     attempt to initialize its schema upon startup
-->
<property>
  <name>datanucleus.schema.autoCreateAll</name>
  <value>false</value>
</property>
<property>
  <name>datanucleus.autoCreateSchema</name>
  <value>false</value>
</property>
<property>
  <name>datanucleus.fixedDatastore</name>
    <value>true</value>
</property>
<property>
  <name>datanucleus.connectionPool.minPoolSize</name>
    <value>0</value>
</property>
<property>
  <name>datanucleus.connectionPool.initialPoolSize</name>
    <value>0</value>
</property>
<property>
  <name>datanucleus.connectionPool.maxPoolSize</name>
    <value>1</value>
</property>
<property>
  <name>hive.stats.autogather</name>
  <value>false</value>
</property>
       
  <property>
    <name>mapred.reduce.tasks</name>
    <value>100</value>
  </property>
  <!-- To mitigate the problem of PROD-4498 and per HIVE-7140, we need to bump the timeout.
       Since the default value of this property used by Impala is 3600 seconds, we will use this value for
       actual deployment
       (http://www.cloudera.com/content/cloudera/en/documentation/core/latest/topics/cm_props_cdh530_impala.html).
  -->
  <property>
    <name>hive.metastore.client.socket.timeout</name>
    <value>3600</value>
  </property>
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/local_disk0/tmp</value>
  </property>

  <property>
    <name>hive.metastore.client.connect.retry.delay</name>
    <value>10</value>
  </property>
  <property>
    <name>hive.metastore.failure.retries</name>
    <value>30</value>
  </property>


</configuration>
     
"""

dbutils.fs.put(
    file = "dbfs:/FileStore/hive/conf/hive-site.xml",
    contents = contents,
    overwrite = True,
)
```

Environment Variablesにて下記の値を設定。

```EnvironmentVariables
CONF_HIVE_CONF_DIR=/dbfs/FileStore/hive/conf/hive-site.xml
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f0c85ed9-5587-e6a3-5f67-96b1fd3add01.png)


### 課題
#### 課題4-1. 外部メタストアデータベースへ接続できず、データベースを作成しても永続化されないこと
データベース作成することは可能ですが、Hive Metastoreデータベースにもデータが書き込まれておりませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/937d7c93-4ed3-a1c6-d920-517a9bbb3251.png)

クラスターを再起動すると、データベースが初期化されてしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/a280102b-7b16-772f-2b20-017418c631eb.png)