1. はじめに
自己紹介
NTTドコモ データプラットフォーム部(以下DP部)の矢野です。
ドコモでは社内データ活用プラットフォームPochiやStreamlit in SnowflakeなどでStreamlitを活用しているのですが、Databricks Appsも今年日本リージョンでもリリースされたということで、実際に使ってみました!
社内データ活用プラットフォームPochi*1とは
私たちDP部は社内のデータ民主化を目指し、StreamlitとGoogle Cloudで圧倒的に使いやすいデータ活用プラットフォームを開発・推進しています。このプラットフォームは、24年度は30万時間もの業務効率化を実現し、直近では5,000人以上の社員に利用が拡大しています。
ASCII.jp:NTTドコモ、Streamlit利用の"ポチポチ分析アプリ"開発で社内データ活用を促進 (1/3)
想定読者
本記事は、以下のような方を想定しています。
- Databricks環境でデータ活用アプリケーションの開発・運用を検討している方
- Streamlitベースのアプリケーション開発に興味がある方
- データガバナンスとセキュリティを重視しながら、アプリケーション開発の効率化を図りたい方
2. Streamlit活用の文脈
ドコモにおけるStreamlit活用の現状
当社では現在、以下の2つのStreamlitプラットフォームを活用しています。
Pochi(Google Cloud基盤):
- フルスクラッチで開発した社内データ活用プラットフォーム
- Vertex AI Geminiとの連携が容易
- 5,000人以上の社員が利用
Streamlit in Snowflake(SiS):
- Snowflakeのマネージドサービス
- データウェアハウスとの統合が強み
- セキュアな環境での開発が可能
Databricks Appsへの興味
2025年、Databricks Appsが日本リージョンでもリリースされたことを知り、以下の点に興味を持ちました。
- Databricksエコシステムとの統合: Unity CatalogやDelta Tableとのシームレスな連携
- マルチLLM対応: Vertex AI GeminiだけでなくClaudeなど複数のLLMに対応
- マネージド環境: PoCやSiSと同様のマネージドサービスとしての運用性
そこで、「実際にどこまで使えるのか?」を検証してみることにしました。
3. Databricks Appsとは
Databricks Appsは、データやAIを活用したアプリケーションをDatabricks上で直接構築・デプロイできるマネージドサービスです。
従来は外部環境でのアプリ開発やインフラ管理が必要でしたが、Databricks Appsを使用することで、Databricks環境内で開発から運用まで完結できます。
主な特徴
Databricksエコシステムとの統合
- Unity Catalog、Delta Table、Vertex AI Gemini/Claudeなどの基盤モデルとシームレスに連携
開発から運用まで一貫した環境
- PaaSとしてOS・ミドルウェア管理の大部分をDatabricksに委任
- Asset BundleによるCI/CD対応
- システムテーブルでのコスト・利用状況監視
Unity Catalogによるガバナンス
- サービスプリンシパルまたはユーザー権限での実行を選択可能
- ユーザー権限を使用することで、個人レベルの監査ログ記録とグループ単位のアクセス制御が可能
今回の検証では、セキュリティとガバナンスの観点からDatabricksのOn-Behalf-Of User Authorizationというユーザー代理認証方式を中心に試してみました。
4. 実際に試してみた
4.1 準備
今回のようにDatabricks Appsを使用するには、以下の前提条件を満たす必要があります
- Databricksワークスペースが適切なリージョンに配置されていること
- Unity Catalogが有効化されていること
- 「Databricks Apps - On-Behalf-Of User Authorization」が有効になっていること(Public Preview)
これらの設定は、Databricksの管理コンソールから確認・設定できます。
Asset Bundleの最小設定
Asset Bundleを使用することで、アプリケーションのデプロイをコード化できます。最小限の設定例は付録の「Asset Bundleの設定例」を参照してください。
アプリケーションの配置
アプリケーションのソースコードは、Asset Bundleでsource_code_pathで指定したディレクトリに配置します。基本的なStreamlitアプリケーションと同じ構成で問題ありません。
4.2 基本機能の検証
基盤モデル連携(Vertex AI Gemini/Claude)
Databricks Appsでは、Serving Endpointsを通じて複数のLLMにアクセスできます。WorkspaceClientを使用することで簡単に基盤モデルを呼び出すことができました(詳細なコード例は付録の「基盤モデル連携のサンプル」を参照)。
実際に試してみたところ、Databricksが提供しているVertex AI GeminiやClaudeといった様々なモデルの切り替えも、エンドポイント名を変更するだけで対応できました。
Asset Bundleでのデプロイ
Asset Bundleを使用したデプロイは、以下のコマンドで実行できます
databricks bundle deploy -t prod
CI/CDパイプラインに組み込むことで、自動デプロイも実現可能です。
システムテーブルでの監視
システムテーブルを使用したSQLクエリで、アプリケーションのコストや利用状況を可視化できました(詳細なクエリ例は付録の「システムテーブルを利用した運用監視」を参照)。現在は制約はありますが、Databricksの統合された環境で運用監視ができるのは便利だと考えます。
現時点での制約:
- 取得可能なメトリクス: コストやDBU使用量など一部の情報のみ取得可能
- ログの揮発性: SQL実行は監査ログに記録されますが、アプリケーション独自のログ(画面操作やエラーログなど)は揮発性のため、長期的な分析には別途実装が必要
コンシューマアクセス機能
Databricksのワークスペース権限の一つで、ビジネスユーザーに必要最小限の権限のみを付与する機能です。
主なメリット:
- ノイズの削減: SQLウェアハウス、クエリー履歴、ノートブックなど開発者向けの情報を非表示にし、ビジネスユーザーに不要な情報を見せない
- 誤操作の防止: 新しいオブジェクトの作成を制限
- シンプルなUX: Databricks One(Public Preview)経由でダッシュボードやアプリのみ利用可能
4.3 ユーザー代理認証の検証
ユーザー代理認証を活用することで、より細やかなアクセス制御を実装できます。
ユーザートークンの取得方法
Databricks Appsでは、ユーザーがアプリにアクセスする際、そのユーザーのアクセストークンが自動的にHTTPヘッダー(X-Forwarded-Access-Token)に格納されます。このトークンを取得してWorkspaceClientに渡すことで、アプリはそのユーザーの権限で各種Databricksリソースにアクセスできます。
user_token = st.context.headers.get('X-Forwarded-Access-Token') w = WorkspaceClient(token=user_token, auth_type="pat")
このクライアントを使って実行される操作(SQL実行、Genie呼び出しなど)は、アプリにアクセスしているユーザーの権限で実行され、監査ログにもそのユーザー名が記録されます。
コンシューマアクセスとの組み合わせ
ユーザー代理認証は、コンシューマアクセス機能と組み合わせることで、ビジネスユーザーにとって最適な体験を提供できます。
コンシューマアクセス権限を持つユーザーがアプリを使用する際も、ユーザー代理認証によりそのユーザーの権限で実行されます。これにより
- ビジネスユーザーはアプリの利用に必要な権限のみ取得し、余計な開発機能や技術的詳細に煩わされない
- Unity CatalogのABACと組み合わせることで、同じアプリでもユーザー・グループごとに異なるデータビューを自動提供
- 監査ログには実際の利用ユーザーが記録され、ガバナンス要件を満たす
この仕組みにより、開発者は権限制御のための複雑なコードを書く必要がなく、Databricksのガバナンス機能に委任できます。
Genie呼び出しの実装
ユーザー権限を使用したGenie(DatabricksのAIアシスタント)呼び出しを実装してみました。詳細なコードは付録に記載していますが、ユーザートークンを使用することで、各ユーザーの権限に基づいたデータアクセスが自動的に制御されることを確認できました。
ABACによる行レベルフィルター
Unity Catalogの属性ベースアクセス制御(ABAC)を使用することで、ユーザーが所属しているグループに基づいて、同じテーブルでも表示される行を自動的に制限できます。
以下は、Databricksの公式サンプルデータセット(diamonds)を使用した実装例です
-- グループ所属に基づいた行レベルフィルター関数の作成 CREATE OR REPLACE FUNCTION can_access_cut(cut_value STRING) RETURNS BOOLEAN RETURN ( -- ユーザーがsalesグループのメンバーか判定しつつCUTがIDEAL/PREMIUMでないことを返す CASE WHEN is_account_group_member('sales') THEN CASE WHEN cut_value IN ('IDEAL', 'PREMIUM') THEN FALSE ELSE TRUE END ELSE TRUE -- salesグループ以外は制限なし END ); -- diamondsテーブルに行フィルターを適用 ALTER TABLE catalog_name.schema_name.diamonds SET ROW FILTER catalog_name.schema_name.can_access_cut ON (cut);
動作確認:
- salesグループのメンバー:
cut列がIDEALまたはPREMIUMの行は自動的に除外され、それ以外の品質グレード(Good、Very Good等)のデータのみ表示 - それ以外のユーザー: 全ての品質グレードのデータが表示(制限なし)
実際に異なるグループに所属するユーザーでテストしたところ、アプリケーション側で権限チェックのコードを書くことなく、自動的に適切なデータビューが提供されることを確認できました。
5. 使ってみた所感
5.1 良かったポイント
Databricksエコシステムとの統合
Unity CatalogやDelta Tableとのシームレスな連携、複数のLLM(Vertex AI Gemini、Claude等)を統一インターフェースで利用できる点が魅力的でした。既存のDatabricks環境を活用している場合、追加の設定なしでデータにアクセスでき、マルチLLM戦略も柔軟に実現できます。PochiではVertex AI Geminiが中心、SiSではSnowflake Cortex経由でのLLM利用となりますが、Databricks Appsは複数のLLMを統一インターフェースで切り替えられる点が便利でした。
責任共有モデルによる運用負荷軽減
PaaSとしてOS層やミドルウェアの管理をDatabricksに委任できる点が大きな利点です。セキュリティパッチの適用やライブラリのバージョン管理といった継続的な運用タスクが削減されつつ、追加パッケージも柔軟に利用できるため、開発の自由度とマネージドの利便性のバランスが取れていると認識しています。Pochiのようなフルスクラッチ開発では運用タスクが継続的に発生し、SiSはパッケージ制限がありますが、Databricks Appsは両者のバランスが良いと考えます。
ユーザー権限ベースの柔軟な制御
Unity CatalogのABACと組み合わせることで、アプリケーション側に複雑な権限チェックロジックを実装することなく、行レベルフィルター設定のみでユーザー単位・グループ単位の細やかなアクセス制御を実現できました。監査ログにも実際の実行ユーザーが記録されるため、ガバナンス要件が厳しい環境でも活用できると認識しています。
5.2 気になったポイント
オートスケール機能の不足
現状、Databricks Appsにはオートスケール機能が提供されていません。
影響:
- 同時アクセス数が急増した場合、パフォーマンスの低下やタイムアウトが発生する可能性がある
- ピーク時のアクセスに合わせたリソース設定が必要となり、非ピーク時のコスト効率が悪くなる
検討が必要なケース:
- 利用者数が不特定多数のアプリケーション
- アクセスパターンが予測困難なアプリケーション
社内の特定部門向けアプリケーションなど、同時アクセス数が比較的予測可能なケースでは大きな問題にはならないと考えられます。PochiやSiSではオートスケールが可能であるため、社内向けであっても多数のユーザが見込まれるユースケースには不向きです。
アプリケーションログの揮発性
SQL実行は監査ログに記録されますが、アプリケーション独自のログ(例:ユーザーの画面操作ログ、エラーログなど)は揮発性です。
影響:
- アプリケーションの再起動やデプロイ時にログが失われる
- 長期的なトラブルシューティングやユーザー行動分析に制限がある
対策:
- 重要なログは、Delta TableやUnity Catalogへの書き込み処理を独自実装する必要がある
6. 使いどころ
検証を通じて、Databricks Appsが適しているケースと適していないケースが見えてきました。
適したユースケース
データガバナンスを重視する社内向けアプリケーション:
- 部門ごとに異なるデータアクセス権限が必要な場合
- ユーザー単位での細かいアクセス制御と監査ログが必須な場合
- Unity Catalogを既に活用している環境
Databricks環境と密接に統合したアプリケーション:
- Delta Tableを活用したデータ分析ツール
- LLMを活用したデータ探索・分析支援アプリケーション
- 既存のDatabricksワークフローと連携するダッシュボード
同時アクセス数が予測可能で安定しているアプリケーション: - 社内の特定部門向けダッシュボードやレポーティングツール - 定期的な利用パターンが確立されている業務アプリケーション - 利用者が限定的な分析ツール
適さないユースケース
同時アクセス数が大幅に変動するアプリケーション:
- 突発的なトラフィック急増が予想されるサービス
- イベント駆動型で利用者数が不特定なアプリケーション
- オートスケールが必須の要件がある場合
詳細なアプリケーションログの長期保存が必須要件:
- コンプライアンス上、すべての操作ログを永続的に保存する必要がある場合
- ユーザー行動の詳細な追跡が必要な場合(別途実装が必要)
7. まとめ
Databricks Appsを実際に検証してみて、私見ですが、Snowflakeは運用性の高いマネージドな環境で最適解を設計していくというイメージに対して、Databricksは開発者に裁量が多い柔軟性・拡張性に優れた基盤というイメージを持ちました。
どちらが優れているということはなく、プロジェクトの状況や要件に応じて適切な環境を選択することが重要だと考えます。
Databricks Appsは2025年にGAされたばかりの新しいサービスです。現時点ではオートスケールやログ保存などいくつかの制約はありますが、Databricksエコシステムとの統合やユーザー権限ベースのガバナンス機能など、データ基盤としての強みを活かせる点が魅力です。
今後も機能の進化を追いかけつつ、適切なユースケースでの活用を検討していきたいと考えています。
8. 付録
Asset Bundleの設定例
Asset Bundleを使用することで、アプリケーションのデプロイをコード化できます。
# databricks.yml bundle: name: ${APP_NAME} resources: apps: deploy_app: name: ${APP_NAME} source_code_path: ../app description: "A Streamlit app that uses a SQL warehouse" resources: - name: "sql-warehouse" description: "A SQL warehouse for app to be able to work with" sql_warehouse: id: ${SQL_WAREHOUSE_ID} permission: "CAN_USE" user_api_scopes: - "sql" targets: # 本番環境用 prod: mode: production workspace: host: ${PROD_DATABRICKS_HOST} root_path: /Workspace/Apps/${APP_NAME}/
引用元: Databricks Asset Bundles のリソース
システムテーブルを利用した運用監視
Databricks Appsのコストや利用状況は、システムテーブルを使用して監視できます。
-- アプリケーションの利用状況とコストを監視 SELECT us.usage_date, us.usage_metadata.app_id, us.usage_metadata.app_name, SUM(us.usage_quantity) AS dbus, SUM(us.usage_quantity * lp.pricing.effective_list.default) AS dollars FROM system.billing.usage us LEFT JOIN system.billing.list_prices lp ON lp.sku_name = us.sku_name AND us.usage_start_time BETWEEN lp.price_start_time AND COALESCE(lp.price_end_time, NOW()) WHERE billing_origin_product = 'APPS' AND us.usage_unit = 'DBU' AND us.usage_date >= DATE_SUB(NOW(), 30) GROUP BY us.usage_date, us.usage_metadata.app_id, us.usage_metadata.app_name ORDER BY us.usage_date DESC
引用元: Databricks Appsのロギングとモニタリング
基盤モデル連携のサンプル
Databricks AppsからServing Endpointsを通じてLLMを呼び出す基本的な実装例です。
import streamlit as st from databricks.sdk import WorkspaceClient w = WorkspaceClient() response = w.serving_endpoints.query( name="endpoint_name", prompt="Databricks Appsについて解説して下さい", temperature=0.5, ) st.json(response.as_dict())
Genie呼び出しの実装サンプル
import streamlit as st from databricks.sdk import WorkspaceClient import pandas as pd user_token = st.context.headers.get('X-Forwarded-Access-Token') w = WorkspaceClient(token=user_token, auth_type="pat") genie_space_id = "xxxx" def display_message(message): if "content" in message: st.markdown(message["content"]) if "data" in message: st.dataframe(message["data"]) if "code" in message: with st.expander("Show generated code"): st.code(message["code"], language="sql", wrap_lines=True) def get_query_result(statement_id): try: result = w.statement_execution.get_statement(statement_id) return pd.DataFrame(result.result.data_array, columns=[i.name for i in result.manifest.schema.columns]) except Exception as e: st.error(f"Failed to get query result for statement_id={statement_id}: {e}") return pd.DataFrame() def process_genie_response(response): for i in response.attachments: if i.text: message = {"role": "assistant", "content": i.text.content} display_message(message) elif i.query: statement_id = getattr(i.query, "statement_id", None) if statement_id: data = get_query_result(statement_id) message = { "role": "assistant", "content": i.query.description, "data": data, "code": i.query.query } display_message(message) else: # statement_idがない場合は、そのメッセージ(descriptionやqueryなど)をそのまま表示 content = i.query.description if hasattr(i.query, "description") else "" code = i.query.query if hasattr(i.query, "query") else "" message = { "role": "assistant", "content": content, "code": code } display_message(message) if "conversation_id" not in st.session_state: st.session_state.conversation_id = None if "chat_history" not in st.session_state: st.session_state.chat_history = [] if prompt := st.chat_input("Ask your question..."): st.session_state.chat_history.append({"role": "user", "content": prompt}) st.chat_message("user").markdown(prompt) with st.chat_message("assistant"): if st.session_state.conversation_id: conversation = w.genie.create_message_and_wait( genie_space_id, st.session_state.conversation_id, prompt ) else: conversation = w.genie.start_conversation_and_wait(genie_space_id, prompt) st.session_state.conversation_id = conversation.conversation_id st.session_state.chat_history.append({"role": "assistant", "content": conversation.attachments[0].text.content if conversation.attachments and conversation.attachments[0].text else ""}) process_genie_response(conversation)
参考リンク
*1:Pochiは社内の開発コードネームです