Streamlit × Snowflake Hybrid Tables で実現する高速なデータアプリケーション

# はじめに
本記事では、Snowflake の HTAP 機能である Hybrid Tables を使って、Snowflake 上で動作する Streamlit アプリ（Streamlit in Snowflake）の DB操作を高速にし、快適なデータアプリケーションを開発してみます。また、その動作の様子や実行時間について、通常テーブルと Hybrid Tables とで比較したいと思います。 

:::note warn
今回の検証はあくまでも簡易的なもので、テーブルの状態（列の数や各レコードの容量など）次第では大きく変わり得ます。通常テーブル・Hybrid Tables 共に、チューニングによっても大幅に結果が変わる可能性があることをご留意ください。
:::

<details><summary>Snowflake とは？</summary>
Snowflakeとは、クラウド型のデータ分析プラットフォームです。コンピューティングとストレージの分離による高いスケーラビリティや、データ共有機能、ガバナンス周りの機能も豊富であることを特徴としています。『AI DATA CLOUD』として、AI、アプリ系の機能にも大きく力を入れ始めています。
</details>

<details><summary>Streamlit in Snowflake とは？</summary>
Snowflake はデータアプリケーションと呼ばれる領域でのサービス投入も頻繁に行っています。特に、Snowflake は 2022年に Streamlit を買収しており、Streamlit in Snowflake という新しいサービスを提供し始めました。

Streamlit は、Python だけで簡単にデータの可視化やインタラクティブなアプリを構築できるオープンソースのライブラリです。これを、Snowflake のマネージドレスなサービスの上で動作させられるのが Streamlit in Snowflake です。

これにより、Snowflake 上のテーブルに簡単にアクセスできたり、大量データをスケーラブルに扱ったり、コンピューティングや認証などのインフラ管理の負担を最小限に抑えられたりと、多様な相互作用が生まれています。
</details>

<details><summary>Snowflake Hybrid Tables とは？</summary>
そうした状況の中、Snowflake は OLAP（分析に特化したテーブル）ワークロードだけでなく、OLTP（トランザクションに特化したテーブル）にも対応できるデータベースをリリースしました。それが、Snowflake Hybrid Tables です。HTAP と呼ばれるサービス群です。

OLAP と OLTP の双方を**同じテーブル**で扱えることで、データの移行（ETL、r-ETL）を行う必要なく、Snowflake の中だけでアプリケーションの多くを完結させることが出来るようになります。
</details>


それでは、Snowflake が目指しリリースした『データアプリケーション』の世界観を体感してまいりましょう。



# 作成するアプリケーション「故障状況管理アプリ」
今回作成するアプリは、故障状況を管理するアプリケーションです。例えば、電波塔でもいいし、アスファルトでもいいし、何でも構いません。これを、高いスケーラビリティで分析でき、運用管理や開発がシンプルに済む Snowflake で構築できないか？を確認していきます。

## 使用するデータの準備
それでは、アプリで使用するデータを準備していきます。比較のため、通常テーブルと Hybrid Tables の双方を用意していきます。また、Hybrid Tables の作成は、CTAS 文で行うことで最適なパフォーマンスでデータロードができるとされていますので、今回もそれに則って作成していきます。なお、今回扱うデータ量は大きいため、再現の際はコストなど注意して行うようにしてください。
```sql
-- 通常のテーブルを作成
CREATE OR REPLACE TABLE fault_locations (
    id NUMBER AUTOINCREMENT PRIMARY KEY,
    latitude DOUBLE,
    longitude DOUBLE,
    status VARCHAR
);

-- 東京近辺に未修理のデータを10件生成
INSERT INTO fault_locations (latitude, longitude, status)
SELECT 
    uniform(35.6695, 35.7095, random()),  -- 緯度 (東京近辺)
    uniform(139.7017, 139.7417, random()),  -- 経度 (東京近辺)
    '未修理'
FROM table(generator(rowCount => 10));

-- 全世界に修理済みのデータを100万件生成
INSERT INTO fault_locations (latitude, longitude, status)
SELECT 
    uniform(-90, 90, random()),  -- 緯度 (全世界)
    uniform(-180, 180, random()),  -- 経度 (全世界)
    '修理済み'
FROM table(generator(rowCount => 1000000));


-- Hybrid Tables を作成
CREATE OR REPLACE HYBRID TABLE fault_locations_ht (
    id NUMBER AUTOINCREMENT PRIMARY KEY,
    latitude DOUBLE,
    longitude DOUBLE,
    status VARCHAR
) as select * from fault_locations;
```


今回は、100万件と1000万件でそれぞれ比較していきます。結果が楽しみですね。

## 作成したアプリのコード
作成したアプリのコード（Streamlit in Snowflake で編集・実行）を下記に示します。修理が必要な赤色のマークを押すと、故障状況を登録できるようになっています。今回は簡易的なものになっていますが、これに例えば緯度経度を表示するようにすれば現地まで向かえるようになりますし、登録時刻や登録者を記録できるようにすれば後々の分析に役立つ情報になります。
```python
import time

import streamlit as st
import pydeck as pdk
from snowflake.snowpark.context import get_active_session
from snowflake.snowpark import functions as F


# Get the current credentials
session = get_active_session()

# 通常／Hybrid Tables の選択
selected_table = st.radio("使用するテーブルを選択してください", options=["通常テーブル", "Hybrid Tables"])

if selected_table == "通常テーブル":
    table_name = "fault_locations"
elif selected_table == "Hybrid Tables":
    table_name = "fault_locations_ht"


# 地図データ
df_fault = session.table(table_name).where("STATUS='未修理'").to_pandas()
df_fault["COLOR"] = [[0, 255, 0]] * len(df_fault)  # デフォルトカラー
for idx, row in df_fault.iterrows():
    if row["STATUS"] == "未修理":
        df_fault.at[idx, "COLOR"] = [255, 0, 0]
    elif row["STATUS"] == "修理中":
        df_fault.at[idx, "COLOR"] = [255, 255, 0]

# 地図表示
event = st.pydeck_chart(
    pdk.Deck(
        map_style=None,
        initial_view_state=pdk.ViewState(
            latitude=35.6895,
            longitude=139.7217,
            zoom=12,
        ),
        layers=[
            pdk.Layer(
                "ScatterplotLayer",
                data=df_fault,
                get_position="[LONGITUDE, LATITUDE]",
                get_color="COLOR",
                get_radius=300,
                pickable=True,
                auto_highlight=True,
                id="map"
            )
        ],
    ),
    selection_mode="single-object",
    on_select="rerun",
)

# 修理状況の更新
st.subheader("修理状況の更新（UPDATE）")
selected_id = None
is_continue = True
if not event == {"selection":{"indices":{},"objects":{}}}:
    selected_id = event["selection"]["objects"]["map"][0]["ID"]
    st.info(f"ID{selected_id}を更新します。")
    is_continue = True
else:
    st.info("更新するIDを選択してください")
    is_continue = False 

if is_continue:
    new_status = st.selectbox(f"新しいステータスを選択", ["未修理", "修理中", "修理完了"])
            
    
    if st.button("ステータスを更新"):
        with st.spinner("ステータスを更新中"):
            start_time = time.time()
            df_fault = session.table(table_name)
            df_fault.update({"status": new_status}, df_fault["id"] == selected_id)
            df_fault.count()
            end_time = time.time()
    
        st.session_state.time_update = end_time - start_time
    
    if "time_update" in st.session_state:
        st.write(f"更新にかかった時間（{selected_table}）：{st.session_state.time_update:.2f}")


# 修理状況の削除
st.subheader("修理状況の削除（DELETE）")

delete_id = st.number_input("削除するIDを選択してください", value=1000)

if st.button("レコードを削除する"):
    with st.spinner("レコードを削除する"):
        start_time = time.time()
        df_fault = session.table(table_name)
        df_fault.delete(df_fault["ID"]==delete_id)
        df_fault.count()
        end_time = time.time()
                
    st.session_state.time_delete = end_time - start_time
    
    if "time_delete" in st.session_state:
        st.write(f"削除にかかった時間（{selected_table}）：{st.session_state.time_delete:.2f}")
```

コードとしては、`st.pydeck_chart` で地図を描画し、クリックした対象を取得できるようにしています。その後、Snowpark DataFrame の update 構文などを利用して、故障状況を更新します。このときの時間を計測するようにします。

また、描画自体が重くなってしまわないように、故障しているレコードのみ描画するようにし、大量に存在する修理済みのレコードは取得していません。

## アプリ動作の様子
ここでは、まずアプリの動作を実際に見てみて、どの程度の差があるのか体感していただこうと思います。

### 通常テーブル（1000万件）時のアプリ操作
下図に、1000万レコードの通常テーブルに対するレコードの更新の様子を示します。
![blog11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/34200ea6-88d8-a6a9-3b27-5e3a35bb0484.gif)

実行結果を見て分かる通り、若干モサっとしてしまっています。耐えられなくはないですが、もし頻繁に更新する必要があるアプリだと、ストレスになってしまいそうです。

### Hybrid Tables（1000万件）時のアプリ操作
続いて、下図に、1000万レコードの Hybrid Tables に対するレコードの更新の様子を示します。
![blog12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/388f4fa9-1fc3-b11e-78b6-06b3ae12ea87.gif)

いかがでしょうか。相当早くなったことがお分かりいただけたと思います。サクサクですね。

## パフォーマンス比較
ここでは、それぞれ3回時間計測をしてみて、どの程度の差があったのか定量的に確認していきます。また、100万件から1000万件にレコード数を増やしたときの劣化についても確認しましょう。

## UPDATE のパフォーマンス比較
下表及び下図に、UPDATE 時の計測結果を示します。

| Operation   | Hybrid Tables | 通常テーブル |
|-------------|---------------|-------------|
| 1000万件    | 0.91          | 6.36        |
| 100万件     | 0.59          | 1.57        |


![visualization (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/3d348eb3-b922-26a7-2893-d773c0ea2600.png)

いかがでしょうか？通常テーブルでは1000万件時の UPDATE の劣化が見られる一方、Hybrid Tables ではそこまで劣化が見られないことも分かりました。もちろん、その速度差も一目瞭然ですね。



## DELETE のパフォーマンス比較
念の為、DELETE についても同様の確認をしておきます。下表及び下図に、DELETE 時の計測結果を示します。

| Operation   | Hybrid Tables | 通常テーブル |
|-------------|---------------|-------------|
| 1000万件    | 0.67          | 6.24        |
| 100万件     | 0.68          | 1.70        |

![visualization (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/fda698ad-fef0-62af-8661-2bce12315b4b.png)

こちらもほぼ同様の結果になりましたね。


## クエリヒストリーの確認

最後に、Snowflake のクエリヒストリーも確認しておこうと思います。アプリから呼び出している都合上、どうしてもターンアラウンドタイムは長くなってしまうので、実際の処理にかかった時間を確認したいです。

### Hybrid Tables のクエリヒストリー
1000万件レコードの Hybrid Tables に対する UPDATE クエリを検証しました。その結果、クエリ全体の実行時間は 73ms で、内訳はコンパイルに 39ms、クエリ実行に 34ms かかっていることが分かりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/f824a14e-e043-9a8f-89a4-7e36294acda5.png)


ここで、コンパイルは Snowflake のクラウドサービスレイヤで行われます。今回、コンパイルに若干時間がかかったのは、クエリが単発で実行され、ウォームアップが行われていなかったためと考えられます。一方で、Hybrid Tables に対するクエリ実行自体は 34ms と非常に高速で、Hybrid Tables のパフォーマンスを十分に発揮できていると言えるでしょう。

### Snowflake 通常テーブルのクエリヒストリー
同様に、1000万件レコードの通常テーブルに対する UPDATE クエリも検証しました。その結果、クエリ全体の実行時間は 6.0s で、内訳はコンパイルに 37ms、クエリ実行に 5.9s かかっていることが分かりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/ad44ed9a-1735-2c17-0549-0e409567450b.png)

これらの結果から、Hybrid Tables は OLTP クエリにおいて、通常テーブルと比較して大幅な高速化が図られていることが分かりました！


## 分析クエリのパフォーマンス比較
また、Hybrid Tables ということで、OLAP ワークロードにも対応していることが特徴の一つでした。そんな訳で、分析クエリについても確認しておきましょう。（今回は100万件のレコードに対して行います。）下記のコードをアプリに追加してみてください。
```python
# 分析クエリ
st.subheader("修理状況テーブルの分析")

if st.button("修理状況ごとの緯度・経度の平均値を取得する"):
    with st.spinner("分析中..."):
        start_time = time.time()
        df_fault = session.table(table_name)
        df_fault = df_fault.group_by("STATUS").agg(
            F.avg("LATITUDE").alias("AVG_LATITUDE"),
            F.avg("LONGITUDE").alias("AVG_LONGITUDE")
        )
        df_fault.count()
        end_time = time.time()
                
    st.session_state.time_analysis = end_time - start_time
    
    if "time_analysis" in st.session_state:
        st.write(f"分析にかかった時間（{selected_table}）：{st.session_state.time_analysis:.2f}")


    st.table(df_fault)
```

分析クエリは非常に単純で、修理状況ごとの緯度経度の平均値を取得するものです。

通常テーブルについては、次のように0.38秒となりました。何回か実行して見た所、おおよそ0.3秒ほどで実行できていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/0b3d1e6d-cd85-bdcc-c410-6c6ff5e44c60.png)

続いては Hybrid Tables で、次のように.0.45秒となりました。こちらも何度か実行し、おおよそ0.5秒ほどで実行できていることが分かりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/426d2c42-0ab4-3d71-8a66-0aef36651e85.png)


Hybrid Tables の分析クエリは通常の Snowflake テーブルと比較しても最大2倍ほどの遅延で済むとイベントで言及されていたこともありますし、実際その通りの結果になっていることが分かります。



# おわりに

今回は、Streamlit in Snowflake から Snowflake Hybrid Tables にアクセスするデータアプリケーションを開発し、その性能を検証、体感することができました。

また、Hybrid Tables は、今回確認したようなポイントルックアップクエリだけでなく、1秒あたりのクエリ処理も非常に高速で、現時点で4000QPSとされています。これにより、多くのアプリ利用にも耐えうるのが特徴的な点です。

Hybrid Tables は、今回示したデータアプリケーション以外にも、データロードなどのステータス管理テーブルや、R-ETL（逆ETL）なしのデータ参照などにも活用できるとされています。ぜひこの機会に、Hybrid Tables を活用して実現できる未来を検討してみましょう！


## 仲間募集

NTTデータ テクノロジーコンサルティング事業本部 では、以下の職種を募集しています。

<details><summary>1. クラウド技術を活用したデータ分析プラットフォームの開発・構築(ITアーキテクト/クラウドエンジニア)</summary>

クラウド／プラットフォーム技術の知見に基づき、DWH、BI、ETL領域におけるソリューション開発を推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/cloud_engineer

</details>

<details><summary>2. データサイエンス領域（データサイエンティスト／データアナリスト）</summary>

データ活用／情報処理／AI／BI／統計学などの情報科学を活用し、よりデータサイエンスの観点から、データ分析プロジェクトのリーダーとしてお客様のDX／デジタルサクセスを推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/datascientist

</details>

<details><summary>3.お客様のAI活用の成功を推進するAIサクセスマネージャー</summary>

DataRobotをはじめとしたAIソリューションやサービスを使って、
お客様のAIプロジェクトを成功させ、ビジネス価値を創出するための活動を実施し、
お客様内でのAI活用を拡大、NTTデータが提供するAIソリューションの利用継続を推進していただく人材を募集しています。
https://nttdata.jposting.net/u/job.phtml?job_code=804

</details>

<details><summary>4.DX／デジタルサクセスを推進するデータサイエンティスト《管理職/管理職候補》</summary>
データ分析プロジェクトのリーダとして、正確な課題の把握、適切な評価指標の設定、分析計画策定や適切な分析手法や技術の評価・選定といったデータ活用の具現化、高度化を行い分析結果の見える化・お客様の納得感醸成を行うことで、ビジネス成果・価値を出すアクションへとつなげることができるデータサイエンティスト人材を募集しています。

https://nttdata.jposting.net/u/job.phtml?job_code=898

</details>


## ソリューション紹介

<details><summary> Trusted Data Foundationについて</summary><div>

～データ資産を分析活用するための環境をオールインワンで提供するソリューション～
https://www.nttdata.com/jp/ja/lineup/tdf/
最新のクラウド技術を採用して弊社が独自に設計したリファレンスアーキテクチャ（Datalake+DWH+AI/BI）を顧客要件に合わせてカスタマイズして提供します。
可視化、機械学習、DeepLearningなどデータ資産を分析活用するための環境がオールインワンで用意されており、これまでとは別次元の量と質のデータを用いてアジリティ高くDX推進を実現できます。

</div></details>

<details><summary> TDFⓇ-AM（Trusted Data Foundation - Analytics Managed Service）について</summary><div>

～データ活用基盤の段階的な拡張支援（Quick Start) と保守運用のマネジメント（Analytics Managed）をご提供することでお客様のDXを成功に導く、データ活用プラットフォームサービス～
https://www.nttdata.com/jp/ja/lineup/tdf_am/
TDFⓇ-AMは、データ活用をQuickに始めることができ、データ活用の成熟度に応じて段階的に環境を拡張します。プラットフォームの保守運用はNTTデータが一括で実施し、お客様は成果創出に専念することが可能です。また、日々最新のテクノロジーをキャッチアップし、常に活用しやすい環境を提供します。なお、ご要望に応じて上流のコンサルティングフェーズからAI/BIなどのデータ活用支援に至るまで、End to Endで課題解決に向けて伴走することも可能です。

</div></details>


<details><summary> NTTデータとDatabricksについて </summary>
NTTデータは、お客様企業のデジタル変革・DXの成功に向けて、「databricks」のソリューションの提供に加え、情報活用戦略の立案から、AI技術の活用も含めたアナリティクス、分析基盤構築・運用、分析業務のアウトソースまで、ワンストップの支援を提供いたします。

https://www.nttdata.com/jp/ja/lineup/databricks/

</details>


<details><summary>NTTデータとTableauについて </summary><div>

ビジュアル分析プラットフォームのTableauと2014年にパートナー契約を締結し、自社の経営ダッシュボード基盤への採用や独自のコンピテンシーセンターの設置などの取り組みを進めてきました。さらに2019年度にはSalesforceとワンストップでのサービスを提供開始するなど、積極的にビジネスを展開しています。

これまでPartner of the Year, Japanを4年連続で受賞しており、2021年にはアジア太平洋地域で最もビジネスに貢献したパートナーとして表彰されました。
また、2020年度からは、Tableauを活用したデータ活用促進のコンサルティングや導入サービスの他、AI活用やデータマネジメント整備など、お客さまの企業全体のデータ活用民主化を成功させるためのノウハウ・方法論を体系化した「デジタルサクセス」プログラムを提供開始しています。
https://www.nttdata.com/jp/ja/lineup/tableau/

</div></details>


<details><summary>NTTデータとAlteryxについて </summary><div>
Alteryxは、業務ユーザーからIT部門まで誰でも使えるセルフサービス分析プラットフォームです。

Alteryx導入の豊富な実績を持つNTTデータは、最高位にあたるAlteryx Premiumパートナーとしてお客さまをご支援します。

導入時のプロフェッショナル支援など独自メニューを整備し、特定の業種によらない多くのお客さまに、Alteryxを活用したサービスの強化・拡充を提供します。

https://www.nttdata.com/jp/ja/lineup/alteryx/

</div></details>

<details><summary>NTTデータとDataRobotについて </summary><div>
DataRobotは、包括的なAIライフサイクルプラットフォームです。

NTTデータはDataRobot社と戦略的資本業務提携を行い、経験豊富なデータサイエンティストがAI・データ活用を起点にお客様のビジネスにおける価値創出をご支援します。

https://www.nttdata.com/jp/ja/lineup/datarobot/

</div></details>

<details><summary> NTTデータとInformaticaについて</summary><div>

データ連携や処理方式を専門領域として10年以上取り組んできたプロ集団であるNTTデータは、データマネジメント領域でグローバルでの高い評価を得ているInformatica社とパートナーシップを結び、サービス強化を推進しています。
https://www.nttdata.com/jp/ja/lineup/informatica/

</div></details>

<details><summary>NTTデータとSnowflakeについて </summary><div>
NTTデータでは、Snowflake Inc.とソリューションパートナー契約を締結し、クラウド・データプラットフォーム「Snowflake」の導入・構築、および活用支援を開始しています。

NTTデータではこれまでも、独自ノウハウに基づき、ビッグデータ・AIなど領域に係る市場競争力のあるさまざまなソリューションパートナーとともにエコシステムを形成し、お客さまのビジネス変革を導いてきました。
Snowflakeは、これら先端テクノロジーとのエコシステムの形成に強みがあり、NTTデータはこれらを組み合わせることでお客さまに最適なインテグレーションをご提供いたします。

https://www.nttdata.com/jp/ja/lineup/snowflake/

</div></details>