<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">
	<title>レンコン畑でつかまえて</title>
	<subtitle></subtitle>
	<link href="https://www.jyuch.dev/feed.xml" rel="self"/>
	<link href="https://www.jyuch.dev/"/>
	
	<updated>2026-10-03T08:23:46Z</updated>
	<id>https://www.jyuch.dev/</id>
	<author>
		<name>jyuch</name>
		<email></email>
	</author>
	<entry>
		<title>Databricksでもdbtを動かしてみたい</title>
		<link href="https://www.jyuch.dev/posts/2026/09-05-databricks-dbt/"/>
		<updated>2026-09-05T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/09-05-databricks-dbt/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Databricksには&lt;a href=&quot;https://www.databricks.com/jp/product/data-engineering/spark-declarative-pipelines&quot;&gt;Apache Spark™ 宣言型パイプライン&lt;/a&gt;といういい感じにデータを精錬するための機能があります。&lt;/p&gt;
&lt;p&gt;同様の機能を持つ製品として&lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt;があります。
DatabricksだけであればSDPで十分ですが、触ったことすらないのは問題かなと思い試してみました。&lt;/p&gt;
&lt;h2&gt;PATの払い出し&lt;/h2&gt;
&lt;p&gt;dbtからDatabricksを触りに行くために、PATを発行しておきます。&lt;/p&gt;
&lt;p&gt;めんどくさかったので私はAPI scopeを&lt;code&gt;all-apis&lt;/code&gt;で発行してしまいましたが、たぶん&lt;code&gt;sql&lt;/code&gt;があれば大丈夫かもしれません。しらんけど&lt;/p&gt;
&lt;h2&gt;dbt環境のセットアップ&lt;/h2&gt;
&lt;p&gt;Databricksの公式ドキュメントに&lt;a href=&quot;https://docs.databricks.com/aws/ja/partners/prep/dbt&quot;&gt;セットアップ方法が載っている&lt;/a&gt;ので、その通りにセットアップします。&lt;/p&gt;
&lt;p&gt;Databricks曰くPythonの仮想環境を別途用意して、その上にインストールしろとのことです。&lt;/p&gt;
&lt;p&gt;私はuvを使用しているので、uv環境セットアップします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv init -p 3.11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dbtの&lt;a href=&quot;https://docs.getdbt.com/docs/local/install-dbt?version=2&quot;&gt;Get started&lt;/a&gt;ではバージョン2を進められるのですが、どうもプレリリースのようなので&lt;code&gt;--prerelease allow&lt;/code&gt;を指定してインストールします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv add dbt --prerelease allow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは、dbtをDatabrickcsにつなげるために&lt;a href=&quot;https://github.com/databricks/dbt-databricks&quot;&gt;Databricksアダプター&lt;/a&gt;をインストールします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv add dbt-databricks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;アダプターのインストールが完了したら、dbtのプロジェクトを初期化します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv run dbt init my_dbt_demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;対話形式でいろいろ聞かれるので、番号で答えたり実際の値を入力したりします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;04:55:39  Running with dbt=1.12.3
04:55:39  Creating dbt configuration folder at C:&#92;Users&#92;someone&#92;.dbt
04:55:39  Setting up your profile.
Which database would you like to use?
[1] databricks
[2] spark

(Don&#39;t see the one you want? https://docs.getdbt.com/docs/available-adapters)

Enter a number: 1 👈 Databricksにつなぐので「１」
host (yourorg.databricks.com): dbc-12345678-abcd.cloud.databricks.com 👈 ワークスペースのホスト
http_path (HTTP Path): /sql/1.0/warehouses/abc123456789a123 👈 SQL WarehousesのHTTPパス
[1] use access token
Desired access token option (enter a number): 1 👈 トークンを使用するので「1」
token (dapiXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX): 👈 トークンを入力
[1] use Unity Catalog
[2] not use Unity Catalog
Desired unity catalog option (enter a number): 1 👈 UCを使用するので「1」
catalog (initial catalog): dbt 👈 カタログ名を入力
schema (default schema that dbt will build objects in): default 👈 スキーマ名を入力
threads (1 or more) [1]: 👈 同時実行するスレッド数を入力？
04:59:06  Profile my_dbt_demo written to C:&#92;Users&#92;someone&#92;.dbt&#92;profiles.yml using target&#39;s profile_template.yml and your supplied values.
04:59:06  Running dbt debug to validate the project...
04:59:06  dbt version: 1.12.3
04:59:06  python version: 3.11.15
04:59:06  python path: C:&#92;dev&#92;try-databricks-dbt&#92;.venv&#92;Scripts&#92;python.exe
04:59:06  os info: Windows-10-10.0.26200-SP0
04:59:06  Using profiles dir at C:&#92;Users&#92;someone&#92;.dbt
04:59:06  Using profiles.yml file at C:&#92;Users&#92;someone&#92;.dbt&#92;profiles.yml
04:59:06  Using dbt_project.yml file at C:&#92;dev&#92;try-databricks-dbt&#92;my_dbt_demo&#92;dbt_project.yml
04:59:06  adapter type: databricks
04:59:06  adapter version: 1.12.5
04:59:06  Configuration:
04:59:06    profiles.yml file [OK found and valid]
04:59:06    dbt_project.yml file [OK found and valid]
04:59:06  Required dependencies:
04:59:06   - git [OK found]

04:59:06  Connection:
04:59:06    host: dbc-52ec1bce-aed0.cloud.databricks.com
04:59:06    http_path: /sql/1.0/warehouses/ca9667106d87a573
04:59:06    catalog: dbt
04:59:06    schema: default
04:59:06  Registered adapter: databricks=1.12.5
04:59:07  Databricks adapter:   SPOG host (host_type=&#39;workspace&#39;): no
04:59:07  Databricks adapter:   workspace_id (from ?o= in http_path): None
04:59:07  Databricks adapter:   databricks-sql-connector version: 4.4.0 (supported)
04:59:07  Databricks adapter:   databricks-sdk version: 0.117.0 (supported)
04:59:21    Connection test: [OK connection ok]

04:59:21  All checks passed!
04:59:21  Your new dbt project &amp;quot;my_dbt_demo&amp;quot; was created!
Initialized new project in C:&#92;dev&#92;try-databricks-dbt&#92;my_dbt_demo&#92;my_dbt_demo

For more information on how to configure the profiles.yml file,
please consult the dbt documentation here:

  https://docs.getdbt.com/docs/configure-your-profile

One more thing:

Need help? Don&#39;t hesitate to reach out to us via GitHub issues or on Slack:

  https://community.getdbt.com/

Happy modeling!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初期化が終わるとプロジェクトのディレクトリが作成されているので、移動します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd .&#92;my_dbt_demo&#92;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちゃんと初期化できているか動作確認をします。
色々出てきますが、&lt;code&gt;All checks passed!&lt;/code&gt;と出ればOKなんだと思います。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv run dbt debug
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;05:02:37  Running with dbt=1.12.3
05:02:37  dbt version: 1.12.3
05:02:37  python version: 3.11.15
05:02:37  python path: C:&#92;dev&#92;try-databricks-dbt&#92;.venv&#92;Scripts&#92;python.exe
05:02:37  os info: Windows-10-10.0.26200-SP0
05:02:38  Using profiles dir at C:&#92;Users&#92;someone&#92;.dbt
05:02:38  Using profiles.yml file at C:&#92;Users&#92;someone&#92;.dbt&#92;profiles.yml
05:02:38  Using dbt_project.yml file at C:&#92;dev&#92;try-databricks-dbt&#92;my_dbt_demo&#92;dbt_project.yml
05:02:38  adapter type: databricks
05:02:38  adapter version: 1.12.5
05:02:38  Configuration:
05:02:38    profiles.yml file [OK found and valid]
05:02:38    dbt_project.yml file [OK found and valid]
05:02:38  Required dependencies:
05:02:38   - git [OK found]

05:02:38  Connection:
05:02:38    host: dbc-52ec1bce-aed0.cloud.databricks.com
05:02:38    http_path: /sql/1.0/warehouses/ca9667106d87a573
05:02:38    catalog: dbt
05:02:38    schema: default
05:02:38  Registered adapter: databricks=1.12.5
05:02:39  Databricks adapter:   SPOG host (host_type=&#39;workspace&#39;): no
05:02:39  Databricks adapter:   workspace_id (from ?o= in http_path): None
05:02:39  Databricks adapter:   databricks-sql-connector version: 4.4.0 (supported)
05:02:39  Databricks adapter:   databricks-sdk version: 0.117.0 (supported)
05:02:42    Connection test: [OK connection ok]

05:02:42  All checks passed!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;モデリングする&lt;/h2&gt;
&lt;h3&gt;サンプルデータの作成&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/integrations/dbt-core-tutorial&quot;&gt;公式のチュートリアル&lt;/a&gt;に沿って、いろいろと試してみましょう。&lt;/p&gt;
&lt;p&gt;チュートリアルははじめにサンプルテーブルを作るように言いますが、なんとコード例が動きません。
ですので、あきらめてそれっぽい等価なSQLに書き換えて実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE TABLE diamonds AS
SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/samples/databricks/datasets/Rdatasets/data-001/csv/ggplot2/diamonds.csv&amp;quot;,
    format =&amp;gt; &#39;csv&#39;,
    header =&amp;gt; true
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;モデルの作成&lt;/h3&gt;
&lt;p&gt;すでに公式チュートリアルを脱線しますが、dbt上でちゃんとリネージを扱えるように先ほど作成したテーブルをソーステーブルとして登録します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;models/sources.yml&lt;/code&gt;みたいなファイルに定義するようです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2

sources:
  - name: diamonds_raw
    catalog: dbt
    schema: default
    tables:
      - name: diamonds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そうすると、&lt;code&gt;{{ source(&#39;diamonds_raw&#39;, &#39;diamonds&#39;) }}&lt;/code&gt;のような表記で参照できるようになるので、各モデル内で参照します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;{{
  config(materialized = &#39;table&#39;, file_format = &#39;delta&#39;)
}}
SELECT
  carat,
  cut,
  color,
  clarity
FROM
  {{ source(&#39;diamonds_raw&#39;, &#39;diamonds&#39;) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;{{
  config(materialized = &#39;materialized_view&#39;)
}}
SELECT DISTINCT
  color
FROM
  {{ ref(&#39;diamonds_four_cs&#39;) }}
ORDER BY
  color ASC
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  color,
  avg(price) AS price
FROM
  {{ source(&#39;diamonds_raw&#39;, &#39;diamonds&#39;) }}
GROUP BY
  color
ORDER BY
  price DESC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コマンドを実行すると、それぞれテーブルやマテリアライズドビュー・ビューが生えてきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv run dbt run
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;07:37:47  Running with dbt=1.12.3
07:37:48  Registered adapter: databricks=1.12.5
07:37:48  [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- models.my_dbt_demo.example
07:37:48  Found 3 models, 1 source, 773 macros
07:37:48
07:37:48  Concurrency: 1 threads (target=&#39;dev&#39;)
07:37:48
07:37:54  1 of 3 START sql table model default.diamonds_four_cs .......................... [RUN]
07:37:57  1 of 3 OK created sql table model default.diamonds_four_cs ..................... [OK in 3.73s]
07:37:57  2 of 3 START sql view model default.diamonds_prices ............................ [RUN]
07:37:59  2 of 3 OK created sql view model default.diamonds_prices ....................... [OK in 1.92s]
07:37:59  3 of 3 START sql materialized_view model default.diamonds_list_colors .......... [RUN]
07:38:47  3 of 3 OK created sql materialized_view model default.diamonds_list_colors ..... [OK in 47.59s]
07:38:47
07:38:47  Finished running 1 materialized view model, 1 table model, 1 view model in 0 hours 0 minutes and 58.55 seconds (58.55s).
07:38:47
07:38:47  Completed successfully
07:38:47
07:38:47  Done. PASS=3 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/09-05-databricks-dbt/created-tables.png&quot; alt=&quot;作成されたテーブル&quot;&gt;&lt;/p&gt;
&lt;h3&gt;リネージの確認&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv run dbt docs generate
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;uv run dbt docs serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;とすると、ローカルサーバーが起動してモデル間のリネージが見られるようになります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/09-05-databricks-dbt/dbt-dag.png&quot; alt=&quot;生成されたリネージ図&quot;&gt;&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;いったんはここまでとしますが、中身はJinjaテンプレートらしく、いろいろと作りこめるそうなので今後もいろいろと触ってみたいですね。&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>Databricks SDKでも統合認証を使いたい</title>
		<link href="https://www.jyuch.dev/posts/2026/08-14-databricks-sdk-auth/"/>
		<updated>2026-08-14T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/08-14-databricks-sdk-auth/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Databricksでは&lt;a href=&quot;https://docs.databricks.com/aws/ja/dev-tools/auth/unified-auth&quot;&gt;統合認証&lt;/a&gt;と呼ばれる、ツールやSDKの言語に関わらず統一的な方法で認証を行う方法が用意されています。&lt;/p&gt;
&lt;p&gt;その中でも、今回はなんだかんだ言って色々お世話になりそうな&lt;a href=&quot;https://docs.databricks.com/aws/ja/dev-tools/auth/env-vars&quot;&gt;環境変数を使用した認証&lt;/a&gt;を試してみます。&lt;/p&gt;
&lt;p&gt;ここで紹介されている環境変数はSDKだけでなく、CLIやTerraformでの認証にも使うことが出来ます。&lt;/p&gt;
&lt;h2&gt;認証を行うサンプルコード&lt;/h2&gt;
&lt;p&gt;今回は&lt;a href=&quot;https://docs.databricks.com/gcp/ja/dev-tools/sdk-python&quot;&gt;Databricks SDK for Python&lt;/a&gt;を使用します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from databricks.sdk import WorkspaceClient

w = WorkspaceClient()

for c in w.catalogs.list():
    print(c.name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;みたいなコードを用意し、&lt;a href=&quot;https://docs.astral.sh/uv/concepts/configuration-files/#environment-variable-files&quot;&gt;uvの環境変数読み込み機能&lt;/a&gt;を使用して環境変数経由で認証情報を注入します。&lt;/p&gt;
&lt;p&gt;認証がうまくいけば、認証されたユーザ・サービスプリンシパルの権限に応じたカタログの一覧が見えるはずです。&lt;/p&gt;
&lt;p&gt;以下のサンプルでは&lt;code&gt;DATABRICKS_AUTH_TYPE&lt;/code&gt;を使用して認証方法の指示をSDKに与えていますが、与えない場合は&lt;a href=&quot;https://docs.databricks.com/aws/ja/dev-tools/auth/unified-auth#%E8%AA%8D%E8%A8%BC%E6%96%B9%E6%B3%95%E3%81%AE%E5%84%AA%E5%85%88%E5%BA%A6&quot;&gt;認証方法の優先度&lt;/a&gt;に応じて認証を試行します。&lt;/p&gt;
&lt;h2&gt;Databricks CLIの認証情報を使用した認証&lt;/h2&gt;
&lt;p&gt;Databricks CLIを使用してOAuth U2M認証を行ってから、その認証情報を使ってSDKの認証を行います。&lt;/p&gt;
&lt;p&gt;使い方はとっても簡単で、&lt;code&gt;databricks auth login&lt;/code&gt;でCLI上でログインしてから以下の環境変数をセットするだけです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DATABRICKS_CONFIG_PROFILE&lt;/code&gt;は&lt;code&gt;DEFAULT&lt;/code&gt;以外のプロファイル名を使用したいときに設定します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;DATABRICKS_HOST=https://dbc-12345678-abcd.cloud.databricks.com
DATABRICKS_AUTH_TYPE=databricks-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Personal Access Tokenを使用した認証&lt;/h2&gt;
&lt;p&gt;PATを発行して、そのPATでアクセスします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;DATABRICKS_HOST=https://dbc-12345678-abcd.cloud.databricks.com
DATABRICKS_AUTH_TYPE=pat
DATABRICKS_TOKEN=&amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;サービスプリンシパルのシークレットを使用した認証&lt;/h2&gt;
&lt;p&gt;何らかの処理を自動化したい場合、サービスプリンシパルを使うことも多いと思います。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;DATABRICKS_HOST=https://dbc-12345678-abcd.cloud.databricks.com
DATABRICKS_AUTH_TYPE=oauth-m2m
DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000
DATABRICKS_CLIENT_SECRET=&amp;lt;secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>AWS Summit Japan 2026 に参加してきたお話</title>
		<link href="https://www.jyuch.dev/posts/2026/06-28-aws-summit-japan-2026/"/>
		<updated>2026-06-28T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/06-28-aws-summit-japan-2026/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;今年もAWS Summit Japanに参加してきたので、そのお話です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/06-28-aws-summit-japan-2026.md/board.png&quot; alt=&quot;1日目終了時に落書きボード&quot;&gt;&lt;/p&gt;
&lt;h2&gt;全体的なお話&lt;/h2&gt;
&lt;p&gt;全体を通して感じたのが、「AI（エージェント）が当たり前になった」という点です。&lt;/p&gt;
&lt;p&gt;企業でAIエージェントをデプロイするのは当たり前として、どのように使えるエージェントをコスト効率よくデプロイするかという実用面に軸足が移ったと感じました。&lt;/p&gt;
&lt;p&gt;各社フロンティアモデルであれば業務を遂行させるのに十分な性能を持っており、あとはAIにどのようにコンテキストを与えますか？エージェントの設計はどのようにしますか？という方向にトレンドが移っていると感じます。&lt;/p&gt;
&lt;p&gt;そんな感じのお仕事をしている私にとってはワクワクした内容でしたが、全体的に展示内容としては猫も杓子もAIになっているので、AI周りに食傷気味な方にとっては少しつまらない内容になってたかもしれません。&lt;/p&gt;
&lt;h2&gt;AWS謹製のオントロジー&lt;/h2&gt;
&lt;p&gt;個人的に一番興味深かったのが、AWSが開発中としているオントロジー製品（Project Orion）のデモの展示があったことです。&lt;/p&gt;
&lt;p&gt;既存のRDSやJDBC・Snowflake（や、たぶんDatabricksも）をラップする形で定義する、既存のDB構造やドキュメントからオントロジーを構築し人間が承認する形式を取るとのことでした。&lt;/p&gt;
&lt;p&gt;現在は規制業界でAIエージェントが厳格に回答をしないといけない領域をターゲットとしているらしく、デモでは某金融機関のロゴが見えてました。（このロゴは見えていいのでしょうか？）&lt;/p&gt;
&lt;p&gt;DatabricksのData + AI Summit 2026でも&lt;a href=&quot;https://www.databricks.com/jp/blog/introducing-genie-one-genie-ontology-and-genie-agents&quot;&gt;Genie Ontology&lt;/a&gt;というオントロジー製品の発表がありましたが、AIにコンテキストを理解させるコンテキストレイヤーとしてオントロジーを各社注目していること、開発を進めていることはAIエージェントを開発している1人として注目せざる負えませんでした。&lt;/p&gt;
&lt;p&gt;オントロジーが一般企業に使いこなせるかどうか・お仕事で使う（使わせてもらえる）かはなんともかんともですが、ひとまずはこのあたりの製品は追い続けたいですね。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;Project Orionは個人的にはビッグニュースなのですが、誰も何も書いてないんですよね。
もしかしたら私が幻覚を見せられていた説や、ほんとはしゃべっちゃいけない内容で途中から見せなくなったとかじゃないですよね・・・&lt;/p&gt;
&lt;h2&gt;追記（2026年8月4日）&lt;/h2&gt;
&lt;p&gt;Project Orionとして展示されていたものは&lt;a href=&quot;https://aws.github.io/context-ontology-accelerator/&quot;&gt;Context Ontology Accelerator&lt;/a&gt;という名前になってOSSで公開されたようです。&lt;/p&gt;
&lt;p&gt;すでにAWS Japanの方が記事を書いて下さっているようなので、そのうち触ってみたいですね。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zenn.dev/aws_japan/articles/context-ontology-accelerator-deploy&quot;&gt;オントロジーで AI に業務知識を渡す — AWS の OSS「Context Ontology Accelerator」を試してみた&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>UUIDv7の基礎</title>
		<link href="https://www.jyuch.dev/posts/2026/06-21-uuidv7-basics/"/>
		<updated>2026-06-21T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/06-21-uuidv7-basics/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;そういえばいつぞやに&lt;a href=&quot;https://www.jyuch.dev/posts/2025/10-12-pg18-uuidv7-is-monotonically-increasing/&quot;&gt;PostgresでのUUIDv7の単調性の保証方法&lt;/a&gt;について解説しましたが、そもそもUUIDv7について書いてないなと思い返したので、&lt;a href=&quot;https://www.docswell.com/s/jyuch/K37PEJ-2026-02-15-180000&quot;&gt;昔の発表資料&lt;/a&gt;を再利用しつつブログにも書いておこうと思います。&lt;/p&gt;
&lt;h2&gt;UUID&lt;/h2&gt;
&lt;p&gt;そもそもUUIDってなんだっけというお話から入ろうと思います。&lt;/p&gt;
&lt;p&gt;仕様から引用すると&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A UUID is 128 bits long and is intended to guarantee uniqueness across space and time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ということで、UUIDは128bitからなる識別子で、空間と時間にわたって一意性を保証します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;00000000-0000-0000-0000-000000000000&lt;/code&gt;みたいな見た目をしており、最新の仕様は&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562&quot;&gt;RFC9562&lt;/a&gt;で規定されています。&lt;/p&gt;
&lt;h2&gt;UUIDの各バージョン&lt;/h2&gt;
&lt;p&gt;UUIDはv1からv8まで規定されています。が、v2は仕様からはハブられているので仕様としては7種類です。
まぁ、実務上だとv4とv7しか使わないと思うのでとりあえず2種類覚えておけば生きていけます。&lt;/p&gt;
&lt;p&gt;v8はベンダー固有ということで、独自のUUID実装を表すバージョンとなっています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;バージョン&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv1&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;100ナノ秒単位のタイムスタンプ＋重複回避用のクロックシーケンス＋ノードID&lt;br&gt;タイムスタンプを下位ビットから並べているせいで生成順にソート出来ない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv2&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;（RFC9562で規定されていないので割愛）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv3&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;名前空間内で名前から一意な値を生成する（MD5）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv4&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;（バージョンとバリアントフィールド以外）完全にランダム&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv5&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;名前空間内で名前から一意な値を生成する（SHA-1）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv6&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv1のタイムスタンプを上位ビットから並べて生成順にソート可能にしたもの&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv7&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;ミリ秒単位のUNIXタイムスタンプ＋ランダム値で生成順にソート可能にしたもの&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;UUIDv8&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;君だけの最強のUUIDを実装しよう&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;UUIDv7を採用するモチベーション&lt;/h2&gt;
&lt;p&gt;上記の表のとおり、v7以外にタイムスタンプを使用している実装としてv1とv6があります。&lt;/p&gt;
&lt;p&gt;そのうち、v1はビットフィールドを下位から並べているせいで解析をしないとソートが出来ません。
ただし、一般的なガイダンスとしてUUIDの解析は避けることが推奨されています。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-6.12&quot;&gt;Section 6.12.&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;また、v1とv6はともにグレゴリオエポックでタイムスタンプが実装されていますが、グレゴリオエポックをIEEE754で実装するのは結構大変です。
仕様としてもグレゴリオエポックをIEEE754で実装するのはまれとしています。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-2.1&quot;&gt;Section 2.1.&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;v1とv6はUUIDの衝突を防ぐ目的でUUIDを生成した端末のMACアドレスを含めますが、仕様としてはセキュリティの目的でMACアドレスをUUID内で使用すべきではないというスタンスです。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-8&quot;&gt;Section 8.&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;また、v7はv1やv6に比べてエントロピー特性を改善しています。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-5.7&quot;&gt;Section 5.7.&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;ということからRFC的にもタイムベースなUUIDを使う場合はv7を採用するべきとしています。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-5.6&quot;&gt;Section 5.6.&lt;/a&gt;）&lt;/p&gt;
&lt;h2&gt;UUIDv7のフィールド及びビットレイアウト&lt;/h2&gt;
&lt;p&gt;v7はバージョンとバリアントを除くと、以下のフィールドから構成されます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;フィールド&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;unix_ts_ms&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;48ビットで表現されたミリ秒単位のUnixエポックタイムスタンプ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;rand_a&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;12ビットのランダムフィールド&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;rand_b&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;62ビットのランダムフィールド&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;img src=&quot;https://www.jyuch.dev/img/2026/06-21-uuidv7-basics/uuidv7_field_and_bit_layout.png&quot;&gt;
&lt;img src=&quot;https://www.jyuch.dev/img/2026/06-21-uuidv7-basics/uuidv7_layout.png&quot; width=&quot;75%&quot;&gt;
&lt;p&gt;&lt;code&gt;unix_ts_ms&lt;/code&gt;はミリ秒なので、同一ミリ秒内で複数回生成した場合は生成順のソート（単調性）を保証できなくなります。&lt;/p&gt;
&lt;h2&gt;追加の単調性を保証するためにRFCで提案されている方法&lt;/h2&gt;
&lt;p&gt;せっかくタイムスタンプを使って単調性を保証できるようにしたのに、なんだか片手落ちですね。&lt;/p&gt;
&lt;p&gt;でももう大丈夫&lt;/p&gt;
&lt;p&gt;RFCでは追加の単調性を保証する以下の3つを提案しています。（&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9562#section-6.2&quot;&gt;Section 6.2. Monotonicity and Counters&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;共通して以下の方法は&lt;code&gt;rand_a&lt;/code&gt;（と&lt;code&gt;rand_b&lt;/code&gt;の左側ビット）を使用して追加の単調性を保証します。
ただし、いずれも状態を保持する必要があるため、基本的にはシングルノードを前提としています。&lt;/p&gt;
&lt;h3&gt;固定ビット長カウンター&lt;/h3&gt;
&lt;p&gt;固定ビット長カウンターでは、&lt;code&gt;rand_a&lt;/code&gt;の一部もしくは全部をカウンターとして使用して、タイムスタンプティック内で生成するたびにカウンターをインクリメントします。&lt;/p&gt;
&lt;p&gt;また、桁数が不足するようであれば&lt;code&gt;rand_b&lt;/code&gt;の左側ビットもカウンターとして拡張します。&lt;/p&gt;
&lt;img src=&quot;https://www.jyuch.dev/img/2026/06-21-uuidv7-basics/fixed_bit-length_dedicated_counter.png&quot; width=&quot;75%&quot;&gt;
&lt;h3&gt;単調増加するランダム値&lt;/h3&gt;
&lt;p&gt;タイムスタンプティック内の初回生成時は普通にv7を生成し、2回目以降は前回生成した値にランダム値を加算していきます。&lt;/p&gt;
&lt;p&gt;この場合、初回にあまりにも大きい値を生成してしまうとすぐにレンジを使いつぶしてしまうので、&lt;code&gt;ramd_a&lt;/code&gt;パートを&lt;code&gt;000&lt;/code&gt;で生成しておくといった工夫も必要になります。&lt;/p&gt;
&lt;img src=&quot;https://www.jyuch.dev/img/2026/06-21-uuidv7-basics/monotonic_random.png&quot; width=&quot;75%&quot;&gt;
&lt;h3&gt;追加のクロック精度の導入&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;rand_a&lt;/code&gt;を追加のタイムスタンプフィールドとし、サブミリ秒をエンコードして格納します。
また、サブミリ秒が衝突した場合はサブミリ秒のティックを進めて衝突を回避します。&lt;/p&gt;
&lt;p&gt;クロック精度がプラットフォームで変わるため、実装上はプラットフォーム毎に生成ロジックを変える必要があります。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;タイムベースのUUIDを使うならUUIDv7を使う
&lt;ul&gt;
&lt;li&gt;UUIDv1 / UUIDv6は互換性が必要な用途以外では非推奨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;タイムスタンプはミリ秒精度
&lt;ul&gt;
&lt;li&gt;サブミリ秒を取り出せる場合もあるが、生成側の実装依存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;実装によっては同一ミリ秒内でも単調性を保証する&lt;/li&gt;
&lt;li&gt;RFCで提案しているのは以下の方法
&lt;ul&gt;
&lt;li&gt;固定ビット長カウンター&lt;/li&gt;
&lt;li&gt;単調増加するランダム値&lt;/li&gt;
&lt;li&gt;追加のクロックの導入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>はじめてのDatabricks on AWS</title>
		<link href="https://www.jyuch.dev/posts/2026/05-05-how-to-deploy-databricks-on-aws/"/>
		<updated>2026-05-05T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/05-05-how-to-deploy-databricks-on-aws/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;上司から「Databricksを使えるようにしておいて」と言われることは長い人生のなかでは一度くらいはあるかもしれません。&lt;/p&gt;
&lt;p&gt;「まぁ、&lt;a href=&quot;https://docs.databricks.com/aws/en/&quot;&gt;公式リファレンス&lt;/a&gt;を見れば何とかなるやろ」と意気揚々と見に行くも、そもそもどこから読み始めたらよいかよくわからないリファレンスを前に心をへし折られた方もいると思います。私です&lt;/p&gt;
&lt;p&gt;ここでは、私が実際にDatabricksをAWSに展開したときに知っておきたかった前提知識や参考になったページ、約1年程度運用して最初から考慮した方がよかったと思うプラクティスなどを紹介します。&lt;/p&gt;
&lt;h2&gt;前提知識&lt;/h2&gt;
&lt;h3&gt;AWS&lt;/h3&gt;
&lt;p&gt;Databricksは基本的には顧客（つまり私たち）のAWS環境にデータを保存したり計算資源を作成します。
&lt;a href=&quot;https://docs.databricks.com/aws/en/admin/workspace/serverless-workspaces&quot;&gt;Serverless workspace&lt;/a&gt;のようなものもあったりしますが、すべてをServerless workspaceで賄うのはたぶん無理だと思うのでいったん忘れてください。&lt;/p&gt;
&lt;p&gt;Databricksでは主に以下のAWSリソースを使用するため、最低限概要をつかんでおく必要があります。&lt;/p&gt;
&lt;h4&gt;ストレージ（S3）&lt;/h4&gt;
&lt;p&gt;Databricksではあらゆる顧客データをS3のバケットに作成します。&lt;/p&gt;
&lt;p&gt;生データはそのままオブジェクトとして保存されますし、テーブルデータは&lt;a href=&quot;https://docs.databricks.com/aws/en/delta/&quot;&gt;Delta Lake&lt;/a&gt;と呼ばれるOpen Table Format（OTF）として保存されます。
ワークスペースで作成したノートブックなども実体はS3に保存されます。&lt;/p&gt;
&lt;p&gt;&lt;s&gt;OTFにはそのほかにも&lt;a href=&quot;https://iceberg.apache.org/&quot;&gt;Apache Iceberg&lt;/a&gt;などがありますが、この段階では気にする必要はありませんし、&lt;a href=&quot;https://www.databricks.com/jp/company/newsroom/press-releases/databricks-agrees-acquire-tabular-company-founded-original-creators&quot;&gt;そのうち違いは無くなるので&lt;/a&gt;将来的にはそんなこともありましたよねという感じになると思います。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;なんだかDatabricks社はOTFをIceberg寄りにしたがってそうな雰囲気をなんとなく感じるので、余裕が出たらIcebergのことも追いかけておくと後々幸せになれるのではないかという気がして来ました。&lt;/p&gt;
&lt;p&gt;また、使うのはS3 Tablesのようなものではなく普通のS3です。
Delta Lake自体にバージョニング機能（タイムトラベルクエリ）も持っているため、バケット側のバージョニングも使いません。&lt;/p&gt;
&lt;h4&gt;計算資源（EC2）&lt;/h4&gt;
&lt;p&gt;Databricksには主に&lt;a href=&quot;https://docs.databricks.com/aws/en/compute/clusters-manage&quot;&gt;クラシックコンピュート&lt;/a&gt;と&lt;a href=&quot;https://docs.databricks.com/aws/en/compute/serverless/&quot;&gt;サーバレスコンピュート&lt;/a&gt;の二種類があります。&lt;/p&gt;
&lt;p&gt;そのうち、クラシックコンピュートは顧客のAWSアカウントで動作するEC2を実体としています。
EC2を稼働させるVPCがオンプレミスやほかのAWSリソースに接続しに行ける構成となっていれば、そのままDatabricksからオンプレミスのデータベースに接続することができます。&lt;/p&gt;
&lt;p&gt;サーバレスコンピュートはDatabricks社のAWSアカウント上のKubernetesクラスタ上で動作しているらしいです。
そのため、サーバレスコンピュート用の計算資源は管理する必要はありません。&lt;/p&gt;
&lt;h4&gt;ネットワーク（VPC・Peering・Direct Connect・Site-to-Site VPN）&lt;/h4&gt;
&lt;p&gt;クラッシックコンピュートの実体がEC2であるならば、そのEC2を稼働させるためのVPCも必要になります。&lt;/p&gt;
&lt;p&gt;Databricksからオンプレミスのデータベースに接続する必要があれば、Direct ConnectやSite-to-Site VPNを使用してオンプレミスとの接続性を確保する必要があります。
同様に、他のVPCのリソースに接続しに行く必要があればVPC Peeringなどで接続性を確保する必要があります。&lt;/p&gt;
&lt;p&gt;この辺はDatabricks固有のお話というよりかは一般的なVPCネットワークの設計のお話となります。&lt;/p&gt;
&lt;h4&gt;Databricksコントロールプレーンへの権限の委譲（IAM）&lt;/h4&gt;
&lt;p&gt;AWSのリソースは顧客のAWSアカウントに作成しますが、クラシックコンピュート起動するたびに手動でEC2インスタンスを作成するわけではありません。&lt;/p&gt;
&lt;p&gt;クロスアカウントアクセスが可能なIAMロールをDatabricksに連携しておいて、Databricksの制御側（この制御側をよくコントロールプレーンと呼びます）から必要なEC2インスタンスを作成したり削除してもらいます。&lt;/p&gt;
&lt;p&gt;また、S3バケットに格納されているデータにアクセスするためのIAMロールなども作成して連携する必要があります。
クラシックコンピュートやサーバレスコンピュートはこのIAMロールをAssume Roleしてデータに触りに行きます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/connect/unity-catalog/cloud-services/service-credentials&quot;&gt;Create service credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/connect/unity-catalog/cloud-storage/&quot;&gt;Connect to cloud object storage using Unity Catalog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Databricks&lt;/h3&gt;
&lt;p&gt;Databricksではトップレベルの要素として、Unity Catalogとワークスペースが存在します。
Databricksの論理設計ではUnity Catalogとワークスペースの設計を最初に行います。&lt;/p&gt;
&lt;p&gt;Unity Catalogの階層構造の設計や、ワークスペースの分割設計が今後のDatabricks運用に多大な影響を与えます。
そのため、この部分の設計はふわっとやらない方が良いと思います。&lt;/p&gt;
&lt;h4&gt;Unity Catalog&lt;/h4&gt;
&lt;p&gt;Unity Catalog（UC）はデータを格納しアクセス権を管理するための入れ物となります。&lt;/p&gt;
&lt;p&gt;UCはAWSの場合、リージョン毎に1つしか作成することができません。&lt;/p&gt;
&lt;p&gt;UCでは&lt;strong&gt;常に&lt;/strong&gt;&lt;code&gt;catalog.schema.object&lt;/code&gt;という3タプルの識別子でオブジェクト（テーブル・ボリューム・モデル）を識別します。2つにしたり4つにしたりは出来ません。
そのため、カタログやスキーマの階層構造の設計はその後のデータ整理に多大な影響を及ぼします。
また、運用途中での変更には多大な労力がかかります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/data-governance/unity-catalog/&quot;&gt;What is Unity Catalog?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;ワークスペース&lt;/h4&gt;
&lt;p&gt;UCがデータの入れ物であるならば、ワークスペースはプログラム（ノートブック・Job・パイプライン・ダッシュボード）と計算資源（クラスタ・AI Search）の入れ物となります。&lt;/p&gt;
&lt;p&gt;ワークスペースは一つのリージョンの中に複数することができます。
ワークスペースはアクセスの境界にもなるので、組織間のアクセス制御にワークスペースを使うことも多いです。&lt;/p&gt;
&lt;h4&gt;クラシックコンピュートとサーバレスコンピュート&lt;/h4&gt;
&lt;p&gt;クラシックコンピュートとサーバレスコンピュートはワークスペース作成時にどちらを使うか選択するようなものではなく、用途によってそれぞれ使い分けを行います。&lt;/p&gt;
&lt;p&gt;サーバレスは制約は多いですが、起動がめっちゃ早いという1点だけでもかなり便利なので、個人的にはサーバレスでは出来ないこと以外は基本的にはサーバレスを使っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クラシックコンピュート
&lt;ul&gt;
&lt;li&gt;メリット
&lt;ul&gt;
&lt;li&gt;VPCの構成によってはオンプレミスのリソースに接続しに行ける&lt;/li&gt;
&lt;li&gt;EC2のインスタンスタイプやクラスタサイズを柔軟に構成できる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;デメリット
&lt;ul&gt;
&lt;li&gt;起動に時間が掛かる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;サーバレスコンピュート
&lt;ul&gt;
&lt;li&gt;メリット
&lt;ul&gt;
&lt;li&gt;起動がほぼ一瞬&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;デメリット
&lt;ul&gt;
&lt;li&gt;（&lt;a href=&quot;https://docs.databricks.com/aws/en/security/network/serverless-network-security/pl-to-internal-network&quot;&gt;PrivteLinkの設定をしないと&lt;/a&gt;）オンプレミスのリソースには接続しに行けないうえに、PrivateLinkは追加のコストが掛かる&lt;/li&gt;
&lt;li&gt;&lt;s&gt;GPUは使えない&lt;/s&gt;プレビューで使えるようになった&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;インフラ設計&lt;/h2&gt;
&lt;h3&gt;ワークスペースとAWSリソースの関係&lt;/h3&gt;
&lt;p&gt;ワークスペースにはEC2を動かすためのアベイラビリティゾーンが異なるサブネットが最低2つと、ワークスペースのリソースを保存するためのS3バケットが1つ必要になります。&lt;/p&gt;
&lt;p&gt;ワークスペースを作成するのに指定したバケットはそのまま&lt;a href=&quot;https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-external-locations&quot;&gt;External locations&lt;/a&gt;としても登録されるので、UCを経由してテーブルなどのUCオブジェクトを保存するのにも使用できます。&lt;/p&gt;
&lt;p&gt;また、ワークスペースのバケット以外にも別途バケットを作成してExternal locationとして登録すれば、UCオブジェクトを登録する専用のバケットとして使用することもできます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/05-05-how-to-deploy-databricks-on-aws/workspace-aws-resources.png&quot; alt=&quot;ワークスペースに紐づくAWSリソース&quot;&gt;&lt;/p&gt;
&lt;h3&gt;VPC構成&lt;/h3&gt;
&lt;p&gt;EC2を動かすためのサブネットは最低限インターネットへの接続性があれば大丈夫です。
また、S3用のゲートウェイエンドポイントには費用が掛からないのと、用意しないとS3のアウトバンドコストがまぁまぁ大変なことになるので最低限S3ゲートウェイエンドポイントは用意しておきます。&lt;/p&gt;
&lt;p&gt;あとは、オンプレミスのデータベースやほかのVPCへの接続性のためにDirect ConnectやVPC Peeringを必要に応じて設定します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/05-05-how-to-deploy-databricks-on-aws/vpc-design-least.png&quot; alt=&quot;最低限の構成&quot;&gt;&lt;/p&gt;
&lt;p&gt;ただし、この構成はクラシックコンピュートとコントロールプレーンの通信はインターネットを経由します。
業界のコンプライアンス要件などでインターネットを経由できない場合、AWSサービスエンドポイントとDatabricksバックエンドサービスへのPrivateLinkを追加で構成することでインターネットへの接続性なしでクラシックコンピュートを運用できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/05-05-how-to-deploy-databricks-on-aws/vpc-design-full.png&quot; alt=&quot;コントロールプレーンとの通信がインターネットを通らない構成&quot;&gt;&lt;/p&gt;
&lt;p&gt;クラシックコンピュートがインターネットへの接続性を持たない構成にすることもできます。
この場合、ノートブックでPythonライブラリをインターネット経由でインストールすることが出来なくなるので、別途プロキシリポジトリを建てるかwheelパッケージをあらかじめダウンロードしてS3などに配置する必要があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/05-05-how-to-deploy-databricks-on-aws/vpc-design-air-gap.png&quot; alt=&quot;エアギャップ構成&quot;&gt;&lt;/p&gt;
&lt;p&gt;厳密なインターネット分離が求められる場合、クラシックコンピュート⇔コントロールプレーン間だけでなくユーザ⇔コントロールプレーン間でもインターネットを経由しないことが求められるかと思います。
その場合、&lt;a href=&quot;https://docs.databricks.com/aws/en/security/network/front-end/front-end-private-connect&quot;&gt;フロントエンドへの接続もインターネットを経由しない構成&lt;/a&gt;にすることができます。&lt;/p&gt;
&lt;p&gt;この辺を真面目にやろうとすると結構大変なので、やる場合は実力のあるサポートベンダに相談した方が良いです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/security/network/classic/privatelink&quot;&gt;Configure classic private connectivity to Databricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/security/network/front-end/front-end-private-connect&quot;&gt;Configure Inbound PrivateLink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;プラクティス&lt;/h2&gt;
&lt;h3&gt;VPCは新規で作成するか？既存のを流用するか？&lt;/h3&gt;
&lt;p&gt;導入企業の既存のAWS環境の設計やポリシーに強く依存しますが、基本的には分けた方が良いと思います。
VPCが分かれてもVPC PeeringでほかのVPCに触りに行くことが出来ますし、Databricks向けのリソースと他のリソースが混ざる心配もありません。&lt;/p&gt;
&lt;p&gt;もっというと、アカウントを分けるぐらいの勢いで行っても良いと思います。&lt;/p&gt;
&lt;h3&gt;サブネットサイズ設計&lt;/h3&gt;
&lt;p&gt;クラシックコンピュートのインスタンスは対コントロールプレーンに1つと、クラスタ内の通信に1つの計2つのプライベートアドレスを使用します。
また、クラスタの再起動時や拡張・縮退時など稼働しているインスタンスよりも多い数のプライベートアドレスを使用することがあります。&lt;/p&gt;
&lt;p&gt;とはいえ、あるワークスペースに割り当てられたサブネットは他のワークスペースに重複して割り当てが出来ないため、過剰に大きいサブネットに分割してしまうとサブネットが足りなくなる場面も出てきます。私です&lt;/p&gt;
&lt;p&gt;個人的には最小のサブネットを&lt;code&gt;/22&lt;/code&gt;として、稼働予定のインスタンスの倍の数を収容できるサブネットに分割するのがいいのかなと思っています。&lt;/p&gt;
&lt;h3&gt;マルチAZ構成&lt;/h3&gt;
&lt;p&gt;ワークスペースに関連付けるサブネットは最低でも2つ以上のAZで構成しなければいけませんが、高可用構成にするには最低でもNAT Gatewayも1つのAZに1つ作成するなど全体として単一障害点をなくさないといけません。&lt;/p&gt;
&lt;p&gt;運用としてはクラシックコンピュートがワークロードの主体になっており、高可用性が求められるのであれば単一障害点をなくす設計を行わないといけません。&lt;/p&gt;
&lt;p&gt;対して、普段のワークロードはサーバレスコンピュートが主体でクラシックコンピュートはオンプレミスに触りに行くだけとかであれば費用対効果の観点から高可用を捨ててしまってもいいかもしれません。&lt;/p&gt;
&lt;h3&gt;リソースの命名規則&lt;/h3&gt;
&lt;p&gt;DatabricksとAWSという異なる世界のリソースを一緒に扱うため、リソースの命名の一貫性には気を付けています。
過去の経緯から一貫性を欠いているものも一部ありますが、こんな感じで短さよりも分かりやすさを優先して命名をしています。&lt;/p&gt;
&lt;h4&gt;Databricks&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;ワークスペース
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{リージョン名}-{連番}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apn1-0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;AWS&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;VPC
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;databricks-vpc-{リージョン}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;databricks-vpc-apn1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;サブネット
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;databricks-subnet-{ワークスペース}-{AZ}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;databricks-subnet-apn1-0-a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;バケット（カタログ）
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;databricks-catalog-{リージョン}-rootbucket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;databricks-catalog-apn1-rootbucket&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;バケット（ワークスペース）
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;databricks-workspace-{ワークスペース名}-rootbucket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;databricks-workspace-apn1-0-rootbucket&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Terraformでのリソースのデプロイ&lt;/h3&gt;
&lt;p&gt;どこまでをTerraformなどのIaCに乗せるかは組織や担当者のIaCへの理解度に依存すると思います。&lt;/p&gt;
&lt;p&gt;私はAWSのリソースはTerraformでデプロイしていますが、Databricksのリソースは手動で展開しています。&lt;/p&gt;
&lt;p&gt;Databricksのリソースはものによっては一度作成したら削除出来ない・一度設定したら削除して再作成しないと変更できないリソースがそこそこあり、無理にTerraform化してよく分からないことになるくらいだったら手動で管理した方が管理しやすいと考えてそのようにしています。&lt;/p&gt;
&lt;h3&gt;カタログ設計&lt;/h3&gt;
&lt;p&gt;UCでは&lt;code&gt;catalog.schema.object&lt;/code&gt;の3タプルでオブジェクトが識別されます。
また、アクセス権は階層構造で設定するので、3タプルの構造がそのままアクセス権の構造に影響します。&lt;/p&gt;
&lt;p&gt;この辺は組織のポリシーやデータの特性に大きく影響を受けるためこれが正解というのはありませんが、一例として以下のようなイメージが考えられます。&lt;/p&gt;
&lt;p&gt;ここでは&lt;a href=&quot;https://www.databricks.com/jp/blog/what-is-medallion-architecture&quot;&gt;メダリオンアーキテクチャ&lt;/a&gt;に従って、&lt;code&gt;販売_bronze&lt;/code&gt;をブロンズ層としてデータの着地点として利用します。&lt;/p&gt;
&lt;p&gt;後続のシルバー層に向けてデータを供給する役目を持つことからブロンズ層はユーザには見せません。
ブロンズ層は可能な限り上流のシステムに近い構成とし、認知負荷を下げます。&lt;/p&gt;
&lt;p&gt;シルバー層ではすべてのユーザが見れる情報と閲覧できる人が限られる情報で分けてカタログに収納します。
閲覧できる人が限られる情報についてはカタログとスキーマで権限を制御します。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;catalog&lt;/th&gt;
      &lt;th&gt;schema&lt;/th&gt;
      &lt;th&gt;table&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td rowspan=&quot;4&quot;&gt;販売_bronze&lt;/td&gt;
      &lt;td rowspan=&quot;3&quot;&gt;販売&lt;/td&gt;
      &lt;td&gt;受注&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;売上&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;商品情報&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CRM&lt;/td&gt;
      &lt;td&gt;顧客情報&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td rowspan=&quot;4&quot;&gt;販売&lt;/td&gt;
      &lt;td rowspan=&quot;3&quot;&gt;販売&lt;/td&gt;
      &lt;td&gt;受注&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;売上&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;商品情報（原価情報なし）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CRM&lt;/td&gt;
      &lt;td&gt;顧客情報（個人情報なし）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td rowspan=&quot;2&quot;&gt;販売_センシティブ&lt;/td&gt;
      &lt;td&gt;原価&lt;/td&gt;
      &lt;td&gt;商品情報（原価情報あり）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CRM&lt;/td&gt;
      &lt;td&gt;顧客情報（個人情報あり）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;ワークスペース設計&lt;/h3&gt;
&lt;p&gt;1年近く運用してみて、ワークスペースは以下のように分割するのがいいのかなという感覚を持っています。
まぁ、あくまでも私が所属している組織で都合が良いというだけで、あらゆる組織に適用できるかというと微妙ですが・・・&lt;/p&gt;
&lt;p&gt;構成としては、ユーザが触る用のワークスペースとデータエンジニアが作業を行うデータの取り込み・パイプライン実行用のワークスペースを最低限分離します。&lt;/p&gt;
&lt;p&gt;これはデータ取り込み用のリソース（ジョブ・パイプライン）と利用者が普段触るリソース（ダッシュボード・Genieスペース）を分離する意図があります。
もちろん、アクセス権を設定すれば触れなくはなりますが、ワークスペースへのアクセス権レベルで制御してしまえば細かいリソース毎のアクセス権は気にしなくてもよくなります。&lt;/p&gt;
&lt;p&gt;また、同時に複数のワークスペースからS3上のDeltaテーブルを変更するとデータが壊れるので、データを変更する操作はなるべく単一のワークスペースに寄せるようにしています。&lt;/p&gt;
&lt;p&gt;また、ある程度の大きさの組織毎にワークスペースを分離します。
ここでの組織とは、組織図に厳密に対応するものではなく、組織の属性に応じたものを考えています。
組織の属性に応じて、どのカタログをアタッチするかの管理を行い、まずはアタッチするカタログレベルでデータへのアクセス権を絞ります。&lt;/p&gt;
&lt;p&gt;もちろん、その先の細かいアクセス権の制御はUnity CatalogなりABACなりで制御を行います。&lt;/p&gt;
&lt;p&gt;同様にDatabricks Appsやダッシュボード・Genieスペースはワークスペースに紐づくため、生産部門が営業部門のアプリに触らないまたはその逆が起こらないという観点からもワークスペースを分離します。&lt;/p&gt;
&lt;p&gt;あとは、たまにやべー挙動をする利用者が出てくると思うので、その監査用に&lt;code&gt;system.audit&lt;/code&gt;スキーマのテーブルを使ってアクティビティ監査を行う専用のワークスペースを置いておきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/05-05-how-to-deploy-databricks-on-aws/workspace-design.png&quot; alt=&quot;ワークスペース設計&quot;&gt;&lt;/p&gt;
&lt;h3&gt;カタログ専用バケットのデプロイ&lt;/h3&gt;
&lt;p&gt;私の組織ではワークスペースとデータを完全に分離したいという考え方からカタログの保存先としてワークスペースのバケットは使用せず、カタログ保存用のバケットを用意したうえでそこに保存しています。&lt;/p&gt;
&lt;p&gt;こうしておけばどのカタログがどのバケットに入っているかなどは気にしなくて良くなるので、組織のニーズに応じて柔軟にワークスペースの作成と削除を行えます。&lt;/p&gt;
&lt;p&gt;ただし、複数のワークスペースから同じテーブルに変更をかけるとデータが壊れるので、テーブルの更新を行うワークスペースには気を付けた方が良いです。&lt;/p&gt;
&lt;h3&gt;新機能の検証用にアメリカリージョンにもワークスペースが欲しいよね&lt;/h3&gt;
&lt;p&gt;Databricksは過去からの傾向として、新機能のPreviewはアメリカのリージョンから利用可能になることが多いです。&lt;/p&gt;
&lt;p&gt;私の組織ではアメリカの主要リージョンの中で日本からのレイテンシが比較的小さいus-west-2（オレゴン）に機能の評価用ワークスペースを構築しています。&lt;/p&gt;
&lt;h3&gt;機能の名前がすごい勢いで変わるので、頑張って追従する&lt;/h3&gt;
&lt;p&gt;Databricksはなぜかすごい勢いで機能の名前を変更します。&lt;/p&gt;
&lt;p&gt;ちょっと思い出すだけでもこのくらいあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI Search（旧：Vector Search）&lt;/li&gt;
&lt;li&gt;Genie One（旧：Databricks One）&lt;/li&gt;
&lt;li&gt;Spark Declarative Pipelines（旧：Delta Live Table）&lt;/li&gt;
&lt;li&gt;Declarative Automation Bundles（旧：Databricks Asset Bundles）&lt;/li&gt;
&lt;li&gt;OpenSharing（旧：Delta Sharing）&lt;/li&gt;
&lt;li&gt;Genie Agents（旧：Genie Spaces）&lt;/li&gt;
&lt;li&gt;Unity Gateway（旧：Unity AI Gateway）← New!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まぁ、ブランディングとか関連機能との整合性とかあるのでしょうがないのかもしれませんが、他の人と機能のことを話す際に「この人はどっちで覚えてるだろうか」みたいな余計な気を遣わないといけないので少し面倒です。&lt;/p&gt;
&lt;h2&gt;その他参考リファレンス&lt;/h2&gt;
&lt;h3&gt;VPC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/security/network/classic/customer-managed-vpc&quot;&gt;Configure a customer-managed VPC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/connect/unity-catalog/cloud-storage/s3/s3-external-location-manual&quot;&gt;Create a storage credential and external location for S3 using Catalog Explorer or SQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/delta/s3-limitations&quot;&gt;Delta Lake limitations on S3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Workspace&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/admin/workspace/create-uc-workspace&quot;&gt;Create a workspace with manual AWS configurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>DatabricksのSpark Declarative Pipelinesを使って簡易的な家計簿を作りたい</title>
		<link href="https://www.jyuch.dev/posts/2026/04-24-databricks-sdp-expenses/"/>
		<updated>2026-04-29T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/04-24-databricks-sdp-expenses/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Databricksには&lt;a href=&quot;https://docs.databricks.com/aws/ja/large-language-models/ai-functions&quot;&gt;AI Functions&lt;/a&gt;があり、ノートブックやSQL Editorなどで使用できます。&lt;/p&gt;
&lt;p&gt;いつからかSpark Declarative Pipelinesでも使えるようになっており、パイプラインだけでOCRと抽出が完結できるようになっていました。&lt;/p&gt;
&lt;p&gt;そこで、今回はボリュームに格納されたレシートから日時と金額を抽出してダッシュボードに表示するまでの処理をSpark Declarative Pipelinesで実装して、Declarative Automation Bundlesでデプロイできるようにしてみました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jyuch/public-expenses&quot;&gt;jyuch/public-expenses&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;パイプラインの実装&lt;/h2&gt;
&lt;p&gt;以下の流れのパイプラインを実装します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Volumeからレシートのイメージをロード&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ai_parse_document&lt;/code&gt;でOCR処理を実行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ai_query&lt;/code&gt;で抽出結果を構造化&lt;/li&gt;
&lt;li&gt;構造化データをフラット化してシルバーテーブルに変換&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基本的に&lt;code&gt;ai_〇〇&lt;/code&gt;は実行にまぁまぁの時間が掛かるので、毎回全部のイメージの再処理をしていたら実行時間がとんでもないことになります。
そのため、ストリーミングテーブルを使用して差分実行を行うようにします。&lt;/p&gt;
&lt;p&gt;この辺も&lt;code&gt;CREATE STREAMING TABLE&lt;/code&gt;と&lt;code&gt;STREAM()&lt;/code&gt;でチェックポイントの管理をしなくても良きにしてくれるのでSDPは大好きです。&lt;/p&gt;
&lt;p&gt;まずはVolumeに格納されているレシートのイメージをロードします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE STREAMING TABLE raw_receipts AS
SELECT
  path,
  modificationTime,
  length,
  content
FROM
  STREAM(READ_FILES(&#39;/Volumes/expenses/default/receipt_images/&#39;, format =&amp;gt; &#39;binaryFile&#39;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そうしたら、&lt;code&gt;ai_parse_document&lt;/code&gt;でOCRを実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE STREAMING TABLE parsed_receipts AS
SELECT
  path,
  modificationTime,
  length,
  content,
  ai_parse_document(content, Map(&#39;version&#39;, &#39;2.0&#39;)) AS parsed_receipt
FROM
  STREAM(raw_receipts);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ai_query&lt;/code&gt;の&lt;code&gt;responseFormat&lt;/code&gt;にJSON Schemaを指定して構造化出力を行わせます。
本当は&lt;code&gt;STRUCT&amp;lt;&amp;gt;&lt;/code&gt;で構造体の定義を指定できるはずなのですが、「Spark Declarative Pipelinesのバグにエンカウントしたのでバグ報告ヨロ」みたいなエラーが表示されるので、あきらめてJSON Schemaを指定しています。&lt;/p&gt;
&lt;p&gt;個人的にはJSON Schemaは書式が冗長なので、できれば&lt;code&gt;STRUCT&amp;lt;&amp;gt;&lt;/code&gt;の方の構文を使いたいです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE STREAMING TABLE structured_receipts AS
SELECT
  path,
  ai_query(
    &#39;databricks-qwen3-next-80b-a3b-instruct&#39;,
    CONCAT(
      &#39;レシートをパースした結果が与えられます。そこから購入日と合計金額を抽出しなさい。&#39;,
      parsed_receipt
    ),
    responseFormat =&amp;gt;
      &#39;{
        &amp;quot;type&amp;quot;: &amp;quot;json_schema&amp;quot;,
        &amp;quot;json_schema&amp;quot;: {
          &amp;quot;name&amp;quot;: &amp;quot;structured_food_receipts&amp;quot;,
          &amp;quot;schema&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
            &amp;quot;properties&amp;quot;: {
              &amp;quot;purchase_date&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                &amp;quot;format&amp;quot;: &amp;quot;date&amp;quot;
              },
              &amp;quot;total_amount&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;
              }
            },
            &amp;quot;required&amp;quot;: [
              &amp;quot;purchase_date&amp;quot;,
              &amp;quot;total_amount&amp;quot;
            ]
          },
          &amp;quot;strict&amp;quot;: true
        }
      }&#39;
  ) AS structured_receipt
FROM
  STREAM(parsed_receipts);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;抽出したパスからレシートの種類（食費とか日用品とか）を付与しています。また、JSON形式の文字列から&lt;code&gt;STRUCT&amp;lt;&amp;gt;&lt;/code&gt;に変換します。&lt;/p&gt;
&lt;p&gt;購入日とかを抽出するタイミングでカテゴライズをしてもいいのですが、精度が出なかったのでひとまずファイルパスで識別させています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE STREAMING TABLE flatten_receipts AS
SELECT
  path,
  regexp_extract(path, &#39;/([^/]+)/[^/]+$&#39;, 1) AS kind,
  try_cast(
    try_parse_json(structured_receipt) AS STRUCT&amp;lt;purchase_date DATE, total_amount INT&amp;gt;
  ) AS receipt
FROM
  STREAM(structured_receipts);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最後にフラット化すればひとまずは家計簿テーブルが完成します。
名前とやっていることが微妙に一致してませんが、まぁ、その・・・。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE MATERIALIZED VIEW receipts AS
SELECT
  path,
  kind,
  receipt.purchase_date,
  receipt.total_amount
FROM
  flatten_receipts;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DABでの展開&lt;/h2&gt;
&lt;p&gt;パイプラインを手動でデプロイしてもいいのですが、全部自動でできた方がうれしいですよね。&lt;/p&gt;
&lt;p&gt;ということでDABで一発デプロイできるようにします。&lt;/p&gt;
&lt;p&gt;特にひねりのない&lt;code&gt;databricks.yml&lt;/code&gt;と&lt;code&gt;resources/expenses.pipeline.yml&lt;/code&gt;を定義します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;bundle:
  name: expenses
  uuid: 00000000-0000-0000-0000-000000000000

include:
  - resources/*.yml

variables:
  catalog:
    description: The catalog to use
  schema:
    description: The schema to use
  ingest_from:
    description: The path log ingestion

targets:
  dev:
    mode: development
    default: true
    variables:
      catalog: expenses
      schema: dev
  prod:
    mode: production
    workspace:
      root_path: /Workspace/Bundles/${bundle.name}/${bundle.target}
    variables:
      catalog: expenses
      schema: prod
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;resources:
  pipelines:
    expenses:
      name: expenses
      catalog: ${var.catalog}
      schema: ${var.schema}
      serverless: true
      root_path: &amp;quot;../src/expenses&amp;quot;

      libraries:
        - glob:
            include: ../src/expenses/transformations/**

      environment:
        dependencies:
          - --editable ${workspace.file_path}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは🚀ボタンで一発デプロイです。&lt;/p&gt;
&lt;h2&gt;ダッシュボードの作成&lt;/h2&gt;
&lt;p&gt;あとはお好みの種類のグラフでダッシュボードを作るだけです。&lt;/p&gt;
&lt;p&gt;ダッシュボードもDABでデプロイ出来ます。
お仕事ではダッシュボードもパイプラインと同じようにDABでデプロイしているのですが、小回りが利かなくなるので趣味ならこの辺はお好みでいいと思います。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;SQLだけでレシートのOCR ⇒ 情報の抽出が完結しました。
また、差分更新も勝手にやってくれます。&lt;/p&gt;
&lt;p&gt;宣言的に実装からデプロイまでできるので管理しやすいです。皆様のお仕事のお供のぜひぜひ&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>Databricks Zerobus Ingestでサーバレスでリアルタイムにデータを流し込みたかった</title>
		<link href="https://www.jyuch.dev/posts/2026/04-04-databricks-zerobus/"/>
		<updated>2026-04-04T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/04-04-databricks-zerobus/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ついにDatabricksのZerobus IngestがGAとなりました。&lt;/p&gt;
&lt;p&gt;Preview版ではデフォルトストレージは対応していないとありましたが、GAになったタイミングでその表示が消えたので、Databricks Free Editionで試してみることにしました。&lt;/p&gt;
&lt;h2&gt;テーブルの作成&lt;/h2&gt;
&lt;p&gt;Databricks上のテーブルからProtobufの定義を生成出来るようなので、まずはテーブルを作成します。&lt;/p&gt;
&lt;p&gt;ここではひとまず私が必要そうなデータ型に絞ってテーブルを作成します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE TABLE workspace.default.zerobus_sample (
    id INT,
    name STRING,
    create_at TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Protobufの定義生成&lt;/h2&gt;
&lt;p&gt;テーブルを作成したら、テーブル定義からProtbufの定義を生成します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/databricks/zerobus-sdk&quot;&gt;Zerobus Ingest SDK&lt;/a&gt;に生成するためのツール（&lt;a href=&quot;https://github.com/databricks/zerobus-sdk/tree/main/rust/tools/generate_files&quot;&gt;generate_files&lt;/a&gt;）があるので、それを使って生成します。&lt;/p&gt;
&lt;p&gt;ツールはサービスプリンシパル経由で触りに行くタイプなので、サービスプリンシパルを作成してシークレットを生成しておきます。&lt;/p&gt;
&lt;p&gt;また、テーブル定義も触りに行く必要があるので、サービスプリンシパルに権限を振るのを忘れないようにして下さい。（1敗）&lt;/p&gt;
&lt;p&gt;ツールはRustを使って実装されているので、Rustの処理系をあらかじめインストールしておきます。
あとは以下のコマンドで生成できます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cargo run -- &#92;
  --uc-endpoint &amp;quot;&amp;lt;your_uc_endpoint&amp;gt;&amp;quot; &#92;
  --client-id &amp;quot;your-client-id&amp;quot; &#92;
  --client-secret &amp;quot;your-client-secret&amp;quot; &#92;
  --table &amp;quot;workspace.default.zerobus_sample&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;クライアントの実装&lt;/h2&gt;
&lt;p&gt;今回はRustで書いていきます。&lt;/p&gt;
&lt;p&gt;必要なライブラリは以下の通りなのですが、&lt;a href=&quot;https://crates.io/crates/databricks-zerobus-ingest-sdk&quot;&gt;databricks-zerobus-ingest-sdk&lt;/a&gt;のprost系は&lt;code&gt;0.13&lt;/code&gt;に依存しているので、prostだけは&lt;code&gt;0.13&lt;/code&gt;を明示的に指定してインストールする必要があります。（1敗）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[dependencies]
# 必須
databricks-zerobus-ingest-sdk = &amp;quot;1.0.1&amp;quot;
prost = &amp;quot;0.13.5&amp;quot;
prost-types = &amp;quot;0.13.5&amp;quot;
tokio = { version = &amp;quot;1&amp;quot;, features = [&amp;quot;full&amp;quot;] }
# お好みで
anyhow = &amp;quot;1&amp;quot;
chrono = &amp;quot;0.4&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは&lt;a href=&quot;https://github.com/databricks/zerobus-sdk/blob/main/rust/examples/proto/single/src/main.rs&quot;&gt;公式のサンプル&lt;/a&gt;をベースに簡単な書き込み処理を実装していきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use crate::samples::TableZerobusSample;
use databricks_zerobus_ingest_sdk::{
    ProtoMessage, StreamConfigurationOptions, TableProperties, ZerobusSdk, ZerobusStream,
};
use prost::Message;
use prost_types::{DescriptorProto, FileDescriptorSet};
use std::fs;

pub mod samples {
    include!(&amp;quot;../output/zerobus_sample.rs&amp;quot;);
}

const SERVER_ENDPOINT: &amp;amp;str = &amp;quot;&amp;lt;workspace id&amp;gt;.zerobus.&amp;lt;region&amp;gt;.cloud.databricks.com&amp;quot;;
const DATABRICKS_WORKSPACE_URL: &amp;amp;str = &amp;quot;https://dbc-12345678-90ab.cloud.databricks.com&amp;quot;;
const DATABRICKS_CLIENT_ID: &amp;amp;str = &amp;quot;00000000-0000-0000-0000-000000000000&amp;quot;;
const DATABRICKS_CLIENT_SECRET: &amp;amp;str = &amp;quot;dose12345678901234567890123456789012&amp;quot;;

#[tokio::main]
async fn main() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    let descriptor_proto = load_descriptor(
        &amp;quot;output/zerobus_sample.descriptor&amp;quot;,
        &amp;quot;zerobus_sample.proto&amp;quot;,
        &amp;quot;table_zerobus_sample&amp;quot;,
    );

    let table_properties = TableProperties {
        table_name: &amp;quot;workspace.default.zerobus_sample&amp;quot;.to_string(),
        descriptor_proto: Some(descriptor_proto),
    };

    let stream_configuration_options = StreamConfigurationOptions {
        max_inflight_requests: 100,
        // RecordType::Proto is the default.
        ..Default::default()
    };

    let sdk_handle = ZerobusSdk::builder()
        .endpoint(SERVER_ENDPOINT)
        .unity_catalog_url(DATABRICKS_WORKSPACE_URL)
        .build()?;

    let mut stream = sdk_handle
        .create_stream(
            table_properties.clone(),
            DATABRICKS_CLIENT_ID.to_string(),
            DATABRICKS_CLIENT_SECRET.to_string(),
            Some(stream_configuration_options),
        )
        .await?;

    ingest_with_offset_api(&amp;amp;mut stream).await?;

    stream.close().await?;
    println!(&amp;quot;Stream closed successfully&amp;quot;);
    Ok(())
}

/// Recommended API: returns offset directly after queuing.
async fn ingest_with_offset_api(stream: &amp;amp;mut ZerobusStream) -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    println!(&amp;quot;=== Offset-based API (Recommended) ===&amp;quot;);

    let now = chrono::Utc::now().timestamp();

    // 1. Auto-encoding: ProtoMessage - pass message directly, SDK handles encoding.
    let order = TableZerobusSample {
        id: Some(1),
        name: Some(&amp;quot;test&amp;quot;.to_string()),
        create_at: Some(now),
    };

    let offset_id = stream.ingest_record_offset(ProtoMessage(order)).await?;
    println!(&amp;quot;[Auto-encoding] Record sent with offset ID: {}&amp;quot;, offset_id);
    stream.wait_for_offset(offset_id).await?;
    println!(
        &amp;quot;[Auto-encoding] Record acknowledged with offset ID: {}&amp;quot;,
        offset_id
    );

    Ok(())
}

// Load descriptor from generated files
fn load_descriptor(path: &amp;amp;str, file: &amp;amp;str, msg: &amp;amp;str) -&amp;gt; DescriptorProto {
    let bytes = fs::read(path).expect(&amp;quot;Failed to read descriptor&amp;quot;);
    let file_set = FileDescriptorSet::decode(bytes.as_ref()).unwrap();

    let file_desc = file_set
        .file
        .into_iter()
        .find(|f| f.name.as_deref() == Some(file))
        .unwrap();

    file_desc
        .message_type
        .into_iter()
        .find(|m| m.name.as_deref() == Some(msg))
        .unwrap()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実装できたら早速実行してみましょう。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;Unsupported table kind. Tables created in default storage are not supported. Error Code: 4024, Error State: 0.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無慈悲&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.databricks.com/aws/en/ingestion/zerobus-limits#workspace-and-target-table&quot;&gt;Zerobus Ingest connector limitations - Workspace and Target table&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;書いてある場所が移動しただけで、使えないんすね。&lt;/p&gt;
&lt;p&gt;Free EditionでもS3を外部ロケーションとして登録してカタログをそっちに作成すれば使えるとは思いますが・・・&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>Hugging FaceのSentence TransformersモデルをUnity Catalogに登録してServing endpointsにデプロイしたい</title>
		<link href="https://www.jyuch.dev/posts/2026/02-26-register-hf-mode-to-uc/"/>
		<updated>2026-02-25T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/02-26-register-hf-mode-to-uc/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;大体タイトル通りです。&lt;/p&gt;
&lt;p&gt;Databricksに最初から登録されている埋め込みモデルは英語のみで日本語に対応していないので、Serving endpointにデプロイするためにHugging Faceから好きなモデルを引っ張ってきてUnity Catalogに登録する方法を確認してみました。&lt;/p&gt;
&lt;p&gt;ちなみにモデルを登録するだけなら以下のノートブックを使えば一発で登録できると思います。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jyuch.dev/img/2026/02-26-register-hf-mode-to-uc/register-hf-model-to-uc.html&quot;&gt;🤗HFのモデルをUCに登録する君&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;🤗HFのモデルをUCに登録する君&lt;/h2&gt;
&lt;h3&gt;環境のセットアップ&lt;/h3&gt;
&lt;p&gt;ノートブックにServerlessコンピュートをアタッチして、以下のライブラリをインストールします。&lt;/p&gt;
&lt;p&gt;MLflowのSentence Transformersを使用したいため、&lt;code&gt;mlflow[sentence-transformers]&lt;/code&gt;を指定してインストールします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mlflow[sentence-transformers]==3.9.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sentence-transformers==5.2.3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回はConfigurationのDependenciesから追加しましたが、たぶん&lt;code&gt;%pip&lt;/code&gt;コマンドを使ってインストールして&lt;code&gt;dbutils.library.restartPython()&lt;/code&gt;しても良いと思います。&lt;/p&gt;
&lt;h3&gt;モデル名の入力とかスキーマの作成とか&lt;/h3&gt;
&lt;p&gt;このノートブックでは登録先のカタログ名とHugging Faceのモデル名をWidgetから受け付けます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;dbutils.widgets.text(&amp;quot;uc_catalog_name&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;UC Catalog name&amp;quot;)
dbutils.widgets.text(&amp;quot;hf_model_name&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;Hugging Face Model name&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登録先のスキーマ名とモデル名は入力されたHugging Faceモデルから組み立てています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;UC_CATALOG_NAME = dbutils.widgets.get(&amp;quot;uc_catalog_name&amp;quot;)
HF_MODEL_NAME = dbutils.widgets.get(&amp;quot;hf_model_name&amp;quot;)
UC_SCHEMA_NAME = HF_MODEL_NAME.split(&amp;quot;/&amp;quot;)[0].replace(&amp;quot;-&amp;quot;, &amp;quot;_&amp;quot;).replace(&amp;quot;.&amp;quot;, &amp;quot;_&amp;quot;)
UC_MODEL_NAME = HF_MODEL_NAME.split(&amp;quot;/&amp;quot;)[1].replace(&amp;quot;-&amp;quot;, &amp;quot;_&amp;quot;).replace(&amp;quot;.&amp;quot;, &amp;quot;_&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そうしたら登録先のスキーマを作成しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;spark.sql(f&amp;quot;CREATE SCHEMA IF NOT EXISTS {UC_CATALOG_NAME}.{UC_SCHEMA_NAME}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MLflowへのモデルのロギング&lt;/h3&gt;
&lt;p&gt;Sentence Transformers経由でHugging Faceのモデルを取得し、&lt;a href=&quot;https://mlflow.org/docs/latest/ml/deep-learning/sentence-transformers/&quot;&gt;MLflowのSentence Transformersフレーバー&lt;/a&gt;を使用してモデルをロギングします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import mlflow
from sentence_transformers import SentenceTransformer
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;mlflow.set_registry_uri(&amp;quot;databricks-uc&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;model = SentenceTransformer(HF_MODEL_NAME)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;input_example = [
    &amp;quot;RFC 1034とRFC 1035は、Domain Name System（DNS）の基礎を定義する最も重要な仕様である。&amp;quot;,
    &amp;quot;Paul Mockapetrisによって1987年に策定され、インターネットにおける名前解決の仕組み全体を規定している。&amp;quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with mlflow.start_run():
    model_info = mlflow.sentence_transformers.log_model(
        model=model,
        name=UC_MODEL_NAME,
        input_example=input_example,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ロギングされたモデルにはモデルの実行に必要なライブラリが含まれるのですが、たまに必要な依存関係が足りずにServing endpointsへのデプロイに失敗することがあるので、事前に一度モデルが動作することを確認します。&lt;/p&gt;
&lt;p&gt;大体10分位掛かるので、気長に待ちましょう。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;mlflow.models.predict(
    model_uri=model_info.model_uri,
    input_data=input_example,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Unity Catalogへの登録&lt;/h3&gt;
&lt;p&gt;モデルが正常に動作することが確認出来たらUnity Catalogへ登録して完了です。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;mlflow.register_model(
    model_info.model_uri, f&amp;quot;{UC_CATALOG_NAME}.{UC_SCHEMA_NAME}.{UC_MODEL_NAME}&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>Databricksのread_files関数でもExcelファイルを読み込みたい</title>
		<link href="https://www.jyuch.dev/posts/2026/01-17-excel-read-files/"/>
		<updated>2026-01-17T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2026/01-17-excel-read-files/</id>
		<content type="html">&lt;h2&gt;2月8日追記&lt;/h2&gt;
&lt;p&gt;現時点ではフリガナ問題は解決しています。&lt;/p&gt;
&lt;p&gt;また、より大きいファイルでも取り込めるようになっていました。
ですが、調子に乗って大きなサイズを放り込むと処理が終わらないので、その辺はいい感じにする必要があります。&lt;/p&gt;
&lt;h2&gt;注意事項&lt;/h2&gt;
&lt;p&gt;本検証は2026年1月17時点の内容で検証しています。
リリース版は挙動が異なる場合があります。&lt;/p&gt;
&lt;p&gt;また、Databricks Free Editionで評価しています。
そのため、商用版と挙動が異なる場合があります。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Databricksの&lt;code&gt;read_files&lt;/code&gt;関数はCSVやJSONフォーマットをいい感じに読み込んでくれる関数です。
その&lt;code&gt;read_files&lt;/code&gt;関数がExcelファイルの読み込みをサポートしました。&lt;/p&gt;
&lt;p&gt;ですが、Excelフォーマットをプログラムで扱う際のトラップに悩まされてきた生産性アプリケーション開発者も多いのではないでしょうか。&lt;/p&gt;
&lt;p&gt;そこで、今回はトラップになりそうな部分の評価を行ってみました。&lt;/p&gt;
&lt;h2&gt;Office Open XMLフォーマット&lt;/h2&gt;
&lt;p&gt;今回はExcelフォーマットのうち、Office Open XMLフォーマット（&lt;code&gt;xlsx&lt;/code&gt;）を評価してみます。
そのOOXMLフォーマットのうち、今回の検証で触れる内容について軽く解説をしていきます。&lt;/p&gt;
&lt;p&gt;OOXMLはXMLの名前がついている通り、主にzipで圧縮されたXMLファイルで構成されています。&lt;/p&gt;
&lt;p&gt;たとえば、以下のようなExcelファイルをzip展開すると、以下のような構造となっています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/example.xlsx.png&quot; alt=&quot;example.xlsxの見た目&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;example.xlsx
│
│  [Content_Types].xml
│
├─docProps
│      app.xml
│      core.xml
│
├─xl
│  │  sharedStrings.xml
│  │  styles.xml
│  │  workbook.xml
│  │
│  ├─printerSettings
│  │      printerSettings1.bin
│  │
│  ├─theme
│  │      theme1.xml
│  │
│  ├─worksheets
│  │  │  sheet1.xml
│  │  │
│  │  └─_rels
│  │          sheet1.xml.rels
│  │
│  └─_rels
│          workbook.xml.rels
│
└─_rels
        .rels
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのうち、内容を確認する際に見るのが以下の二つのファイルになります。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;xl&#92;worksheets&#92;sheet1.xml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xl&#92;worksheets&lt;/code&gt;以下にはシート毎にシートの内容が含まれているXMLファイルが格納されています。&lt;/p&gt;
&lt;p&gt;上記の例だと、こんな感じに内容になるはずです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;worksheet xmlns=&amp;quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;quot;
    xmlns:r=&amp;quot;http://schemas.openxmlformats.org/officeDocument/2006/relationships&amp;quot;
    xmlns:mc=&amp;quot;http://schemas.openxmlformats.org/markup-compatibility/2006&amp;quot;
    mc:Ignorable=&amp;quot;x14ac xr xr2 xr3&amp;quot;
    xmlns:x14ac=&amp;quot;http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac&amp;quot;
    xmlns:xr=&amp;quot;http://schemas.microsoft.com/office/spreadsheetml/2014/revision&amp;quot;
    xmlns:xr2=&amp;quot;http://schemas.microsoft.com/office/spreadsheetml/2015/revision2&amp;quot;
    xmlns:xr3=&amp;quot;http://schemas.microsoft.com/office/spreadsheetml/2016/revision3&amp;quot;
    xr:uid=&amp;quot;{00000000-0001-0000-0000-000000000000}&amp;quot;&amp;gt;
    &amp;lt;dimension ref=&amp;quot;A1:B2&amp;quot; /&amp;gt;
    &amp;lt;sheetViews&amp;gt;
        &amp;lt;sheetView tabSelected=&amp;quot;1&amp;quot; workbookViewId=&amp;quot;0&amp;quot; /&amp;gt;
    &amp;lt;/sheetViews&amp;gt;
    &amp;lt;sheetFormatPr defaultRowHeight=&amp;quot;18.75&amp;quot; /&amp;gt;
    &amp;lt;sheetData&amp;gt;
        &amp;lt;row r=&amp;quot;1&amp;quot; spans=&amp;quot;1:2&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;A1&amp;quot; t=&amp;quot;s&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;0&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
        &amp;lt;row r=&amp;quot;2&amp;quot; spans=&amp;quot;1:2&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;B2&amp;quot; t=&amp;quot;s&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;1&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
    &amp;lt;/sheetData&amp;gt;
    &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;pageMargins left=&amp;quot;0.7&amp;quot; right=&amp;quot;0.7&amp;quot; top=&amp;quot;0.75&amp;quot; bottom=&amp;quot;0.75&amp;quot; header=&amp;quot;0.3&amp;quot; footer=&amp;quot;0.3&amp;quot; /&amp;gt;
    &amp;lt;pageSetup paperSize=&amp;quot;9&amp;quot; orientation=&amp;quot;portrait&amp;quot; r:id=&amp;quot;rId1&amp;quot; /&amp;gt;
&amp;lt;/worksheet&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;A1&lt;/code&gt;と&lt;code&gt;B2&lt;/code&gt;セルにデータが含まれているのが分かりますね。
ですが、謎の数字&lt;code&gt;0&lt;/code&gt;と&lt;code&gt;1&lt;/code&gt;があるだけで、内容は特には記述されていません。&lt;/p&gt;
&lt;p&gt;テキスト情報はどこに行ったのでしょう？&lt;/p&gt;
&lt;p&gt;その答えはShared String Table（SST、&lt;code&gt;sharedStrings.xml&lt;/code&gt;）の中にあります。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;xl&#92;sharedStrings.xml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;SSTはその名の通り、複数の場所から参照されるテキスト情報を格納するためテーブルです。&lt;/p&gt;
&lt;p&gt;上記の例だとシートはSSTのインデックス番号を参照していたことになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;sst xmlns=&amp;quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;quot; count=&amp;quot;2&amp;quot; uniqueCount=&amp;quot;2&amp;quot;&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;Hello Excel&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;こんにちはExcel&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、文字情報は必ずSSTに格納しなければならない訳ではなく、Excelを出力するライブラリはシートのXMLに直接文字を格納する場合が多いです。&lt;/p&gt;
&lt;h2&gt;Excelファイルの読み込み&lt;/h2&gt;
&lt;p&gt;というわけで、Excelの深淵を覗く準備ができたところで&lt;code&gt;read_files&lt;/code&gt;関数でファイルを読み込んでみましょう。&lt;/p&gt;
&lt;h3&gt;単純な例&lt;/h3&gt;
&lt;p&gt;以下のような単純なExcelファイルを用意します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/01_simple_files.xlsx.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;最低限のオプションだけ設定すると以下のように出力されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/01_simple_files.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_01.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;スキーマを推論してくれるので、日付や数字もそれぞれの型として認識してくれています。&lt;/p&gt;
&lt;p&gt;ですが、小数が少し怪しいですね。
内部的にはこの形で格納されているので、&lt;code&gt;read_files&lt;/code&gt;関数が悪いわけではなさそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;worksheet&amp;gt;
    &amp;lt;sheetData&amp;gt;
        &amp;lt;row r=&amp;quot;1&amp;quot; spans=&amp;quot;1:4&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;C1&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;1.1000000000000001&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
        &amp;lt;row r=&amp;quot;2&amp;quot; spans=&amp;quot;1:4&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;C2&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;2.2000000000000002&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
        &amp;lt;row r=&amp;quot;3&amp;quot; spans=&amp;quot;1:4&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;C3&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;3.3&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
        &amp;lt;row r=&amp;quot;4&amp;quot; spans=&amp;quot;1:4&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;C4&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;4.4000000000000004&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
        &amp;lt;row r=&amp;quot;5&amp;quot; spans=&amp;quot;1:4&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;C5&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;5.5&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
    &amp;lt;/sheetData&amp;gt;
&amp;lt;/worksheet&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;型が既知なのであれば、あらかじめスキーマヒントを与えておいたほうが良いかもしれません。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/01_simple_files.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;,
    schemaHints =&amp;gt; &amp;quot;_c2 decimal(5,1)&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_02.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;ちなみに、&lt;code&gt;inferColumnTypes&lt;/code&gt;はExcel読み込みでは考慮されないようです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/01_simple_files.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;,
    inferColumnTypes =&amp;gt; false
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_03.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;スキーマヒントを与えて強制的に文字列とすると、to_stringをしたような形式になります。&lt;/p&gt;
&lt;p&gt;また、日付は&lt;code&gt;MM/DD/YY&lt;/code&gt;形式になります。USロケールなのかもしれません。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/01_simple_files.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;,
    schemaHints =&amp;gt; &amp;quot;_c1 string, _c2 string, _c3 string&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_04.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;日本語を含む例&lt;/h3&gt;
&lt;p&gt;次はよくオフィスで流通していそうなファイルを読み込んでみます。&lt;/p&gt;
&lt;p&gt;今回の検証で初めて知ったのですが、Excelは変換時の元テキストをフリガナとして格納してくれるっぽいです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/02_%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%82%92%E5%90%AB%E3%82%80%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.xlsx.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;関係ないセルを読まないよう、&lt;code&gt;dataAddress&lt;/code&gt;で読み込む場所を明示してあげます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/02_日本語を含むファイル.xlsx&amp;quot;,
    dataAddress =&amp;gt; &amp;quot;&#39;お供　給与表&#39;!B2:C5&amp;quot;,
    headerRows =&amp;gt; 1,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_05.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;良くないですね。&lt;/strong&gt;
フリガナも拾ってきてしまっています。&lt;/p&gt;
&lt;p&gt;少なくとも日本語だと意図してフリガナを拾ってほしいケースはあまりないと思うので、GAまでにフリガナを拾わないか、オプションで選択出来るようになるといいなと思います。&lt;/p&gt;
&lt;p&gt;ちなみに、この例だとSSTはこのようになっています。
シート名として入力したテキストもフォネティックを拾っているとは思いませんでした。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;sst xmlns=&amp;quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;quot; count=&amp;quot;8&amp;quot; uniqueCount=&amp;quot;8&amp;quot;&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;お供&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;1&amp;quot; eb=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;トモ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;きび団子支給数&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;2&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ダンゴ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;4&amp;quot; eb=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;シキュウスウ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;猿&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;1&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;サル&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;犬&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;1&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;イヌ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;雉&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;1&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;キジ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;令和8年1月17日改定&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;レイワ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;3&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ネン&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;5&amp;quot; eb=&amp;quot;6&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ガツ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;8&amp;quot; eb=&amp;quot;9&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ニチ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;9&amp;quot; eb=&amp;quot;11&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;カイテイ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;※変更時はおじいさんとおばあさんの承認を得ること&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;1&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ヘンコウジ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;17&amp;quot; eb=&amp;quot;19&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ショウニン&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;20&amp;quot; eb=&amp;quot;21&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;エ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;全然関係ないデータ&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ゼンゼンカンケイ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;手動でフリガナを消してあげると意図した感じに取り込んでくれます。&lt;/p&gt;
&lt;p&gt;ただし、手動で消すのは結構めんどくさいので、前処理でフリガナを消すプログラムを動かすとかでしょうか・・・&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/03_日本語を含むファイル_フリガナ除去.xlsx&amp;quot;,
    dataAddress =&amp;gt; &amp;quot;&#39;お供　給与表&#39;!B2:C5&amp;quot;,
    headerRows =&amp;gt; 1,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_06.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;sst xmlns=&amp;quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;quot; count=&amp;quot;8&amp;quot; uniqueCount=&amp;quot;8&amp;quot;&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;令和8年1月17日改定&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;レイワ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;3&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ネン&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;5&amp;quot; eb=&amp;quot;6&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ガツ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;8&amp;quot; eb=&amp;quot;9&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ニチ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;9&amp;quot; eb=&amp;quot;11&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;カイテイ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;※変更時はおじいさんとおばあさんの承認を得ること&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;1&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ヘンコウジ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;17&amp;quot; eb=&amp;quot;19&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ショウニン&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;rPh sb=&amp;quot;20&amp;quot; eb=&amp;quot;21&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;エ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;全然関係ないデータ&amp;lt;/t&amp;gt;
        &amp;lt;rPh sb=&amp;quot;0&amp;quot; eb=&amp;quot;4&amp;quot;&amp;gt;
            &amp;lt;t&amp;gt;ゼンゼンカンケイ&amp;lt;/t&amp;gt;
        &amp;lt;/rPh&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;お供&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;きび団子支給数&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;猿&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;犬&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
    &amp;lt;si&amp;gt;
        &amp;lt;t&amp;gt;雉&amp;lt;/t&amp;gt;
        &amp;lt;phoneticPr fontId=&amp;quot;1&amp;quot; /&amp;gt;
    &amp;lt;/si&amp;gt;
&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;計算式を含む例&lt;/h3&gt;
&lt;p&gt;次は式を含むExcelファイルを読み込ませてみます。&lt;/p&gt;
&lt;p&gt;ドキュメントページでは&lt;a href=&quot;https://docs.databricks.com/aws/ja/query/formats/excel&quot;&gt;評価された数式を取り込みます&lt;/a&gt;とありますが、どのような意味なのでしょうか。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/04_formula.xlsx.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/04_formula.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_07.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;エクセルで数式セルを作成すると、内部的には数式と共に計算済みの値が格納されています。
ドキュメントの「評価された数式を取り込みます」とはおそらく内部的に保持されている計算済みの値を取り込むということを言っているのだと思われます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;
&amp;lt;worksheet&amp;gt;
    &amp;lt;sheetData&amp;gt;
        &amp;lt;row r=&amp;quot;1&amp;quot; spans=&amp;quot;1:3&amp;quot;&amp;gt;
            &amp;lt;c r=&amp;quot;A1&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;1&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
            &amp;lt;c r=&amp;quot;B1&amp;quot;&amp;gt;
                &amp;lt;v&amp;gt;2&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
            &amp;lt;c r=&amp;quot;C1&amp;quot;&amp;gt;
                &amp;lt;f&amp;gt;A1+B1&amp;lt;/f&amp;gt;
                &amp;lt;v&amp;gt;3&amp;lt;/v&amp;gt;
            &amp;lt;/c&amp;gt;
        &amp;lt;/row&amp;gt;
    &amp;lt;/sheetData&amp;gt;
&amp;lt;/worksheet&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;では、計算済みの値が格納されないケースがあるのでしょうか。&lt;/p&gt;
&lt;p&gt;全部のケースを確認した訳ではありませんが、ライブラリを使用してExcelファイルをプログラムから作成する場合は数式のみが格納されて計算済みの値が格納されない場合が多いです。&lt;/p&gt;
&lt;p&gt;たとえば、以下のようなコードを使用して上記のExcelと同じようなファイルを作成したとします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import openpyxl


def main():
    wb = openpyxl.Workbook()
    sheet = wb.active

    for it in range(1, 5):
        sheet.cell(it, 1).value = it
        sheet.cell(it, 2).value = it + 1
        sheet.cell(it, 3).value = f&amp;quot;=A{it}+B{it}&amp;quot;

    wb.save(&amp;quot;05_formula_no_value.xlsx&amp;quot;)


if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Excelで開くと未計算のセルは計算されて表示されますが、&lt;code&gt;read_files&lt;/code&gt;関数では計算済みの値が無いため、正しく取得できていません。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/05_formula_no_value.xlsx.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/05_formula_no_value.xlsx&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_08.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;複数のファイル&lt;/h3&gt;
&lt;p&gt;問題ないとは思いますが、念のため複数のファイルの読み込みを試してみます。&lt;/p&gt;
&lt;p&gt;以下のようなコードで100行×100ファイルのExcelを生成して読み込んでみます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import random
import string

import openpyxl


def randomname(n):
    return &amp;quot;&amp;quot;.join(random.choices(string.ascii_letters + string.digits, k=n))


def main():
    for i in range(1, 101):
        print(f&amp;quot;06_excel_files&#92;&#92;book_{i:0&amp;gt;3}.xlsx&amp;quot;)
        wb = openpyxl.Workbook()
        sheet = wb.active

        for r in range(1, 101):
            sheet.cell(r, 1).value = i
            sheet.cell(r, 2).value = r

            for c in range(3, 6):
                sheet.cell(r, c).value = randomname(10)

            for c in range(6, 9):
                sheet.cell(r, c).value = random.randint(0, 100)

        wb.save(f&amp;quot;06_excel_files&#92;&#92;book_{i:0&amp;gt;3}.xlsx&amp;quot;)


if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  *
FROM
  read_files(
    &amp;quot;/Volumes/excel_read_files/default/files/06_excel_files/&amp;quot;,
    format =&amp;gt; &amp;quot;excel&amp;quot;,
    schemaEvolutionMode =&amp;gt; &amp;quot;none&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/output_09.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;良さそうですね。&lt;/p&gt;
&lt;h3&gt;サイズの大きいファイル&lt;/h3&gt;
&lt;p&gt;JavaでExcelを読み書きする代表的なライブラリと言えば&lt;a href=&quot;https://poi.apache.org/&quot;&gt;Apache POI&lt;/a&gt;があります。&lt;/p&gt;
&lt;p&gt;POIはUser API（Excelファイルをインメモリで展開するAPI）で巨大なファイルを扱おうとするとOOMでプログラムが爆散するという悲しき事故が発生しがちです。&lt;/p&gt;
&lt;p&gt;SparkはScala、すなわちJVMで動作するので同じ事故が発生するか否かは割と気になりますよね。&lt;/p&gt;
&lt;p&gt;以下のようなコードでそれぞれの行数を持つExcelファイルを作って、順番に読み込ませてみます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import random
import string

import openpyxl

ROWS = [
    10000,
    10100,
    10101,
    10102,
    10103,
    10104,
    10105,
    10106,
    10107,
    10108,
    10109,
    10110,
    10120,
    10130,
    10140,
    10150,
    10160,
    10170,
    10180,
    10190,
    10200,
    10300,
    10400,
    10500,
    11000,
]


def randomname(n):
    return &amp;quot;&amp;quot;.join(random.choices(string.ascii_letters + string.digits, k=n))


def main():
    for row_count in ROWS:
        print(f&amp;quot;07_large_excel_files&#92;&#92;book_{row_count}.xlsx&amp;quot;)
        wb = openpyxl.Workbook()
        sheet = wb.active

        for r in range(1, row_count + 1):
            sheet.cell(r, 1).value = row_count
            sheet.cell(r, 2).value = r

            for c in range(3, 6):
                sheet.cell(r, c).value = randomname(10)

            for c in range(6, 9):
                sheet.cell(r, c).value = random.randint(0, 100)

        wb.save(f&amp;quot;07_large_excel_files&#92;&#92;book_{row_count}.xlsx&amp;quot;)


if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;詳細は省きますが、私の環境では10102行のファイルまでなら読み込んでくれましたが、10103行のファイルからは関数が返ってこなくなりました。&lt;/p&gt;
&lt;p&gt;関数の内部的な制限に引っかかったのか、それともDatabricks Free Editionのなんらかのレートリミットに引っかかったのかは分かりませんが、あまり大量の行数を有するファイルの読み込みには向かないようです。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;いくつか気になる点はありましたが、ベータ版の機能なのでGAまでには修正されると思います。&lt;/p&gt;
&lt;p&gt;Pythonでロジックを書かなくてもいい感じにExcelファイルが読み込めると、ファイルサーバ内に眠っているExcelファイルのデータの利活用のハードルも低くなると思います。&lt;/p&gt;
&lt;p&gt;GAが待ち遠しい機能ですね。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jyuch/excel-read-files&quot;&gt;jyuch / excel-read-files&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;余談&lt;/h2&gt;
&lt;p&gt;&lt;s&gt;ファイルをドラッグアンドドロップでテーブルを作れる機能も内部的に&lt;code&gt;read_files&lt;/code&gt;関数を使っているのか、現時点では同様に日本語のフリガナを拾ってしまいます。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;こちらも解決済みです。やったぜ&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.jyuch.dev/img/2026/01-17-excel-read-files/gui_ingestion.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
	<entry>
		<title>2025年の振り返りとか</title>
		<link href="https://www.jyuch.dev/posts/2025/12-31-look-back-2025/"/>
		<updated>2025-12-31T00:00:00Z</updated>
		<id>https://www.jyuch.dev/posts/2025/12-31-look-back-2025/</id>
		<content type="html">&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;2025年の振り返りです。&lt;/p&gt;
&lt;h2&gt;Databricks&lt;/h2&gt;
&lt;p&gt;今年はお仕事でDatabricksをかなり触ってきました。&lt;/p&gt;
&lt;p&gt;特にDatabricksの環境を完全にゼロから（新規契約のAWSアカウントから）構築するのはあまりない経験だと思うので、珍しい経験が出来たなと思います。&lt;/p&gt;
&lt;p&gt;他にも基幹システムからのデータ取り込みやデータ加工パイプラインの構築・ガバナンス設計など&lt;s&gt;運用の全部を一人でやらさせられてる&lt;/s&gt;大変貴重な経験を積ませていただいており大変うれしく思います。&lt;/p&gt;
&lt;p&gt;個人的にはDatabricksは少なくとも数年はデータとAI領域でのトップを走るプロダクトだと思っているので、しばらくはDababricksのお仕事を続けたいなとは思ってます。&lt;/p&gt;
&lt;h2&gt;生成AI&lt;/h2&gt;
&lt;p&gt;データ基盤を触っているとAI絡みのお話を頂くので、RAGや&lt;a href=&quot;https://www.jyuch.dev/posts/2025/12-28-dspy-kasu-no-uso/&quot;&gt;プロンプト自動最適化&lt;/a&gt;などのテクノロジはちょこちょこと触っていました。
特にDSPyとMLflowはかなり注目しているので、2026年もこれらのプロダクトを追いかけたいと思っています。&lt;/p&gt;
&lt;p&gt;ただ、AIエージェントは技術的な領域としては面白いのですが、全社的なワークロードを動かしたときの性能面・コスト面の監視や基幹システムとの連接など考えないといけないことが多いなと現時点でも感じています。
2026年はこの辺の本格的な開発・運用が始まりそうなので2026年はつらみポイントが高い年になりそうです。&lt;/p&gt;
&lt;p&gt;この辺を真面目にやろうとすると事業会社の内部でどうこう出来るレベルを超えそうと思っているのですが、皆さんどう考えてるんでしょうね？&lt;/p&gt;
&lt;h2&gt;資格とか&lt;/h2&gt;
&lt;p&gt;今年は&lt;a href=&quot;https://www.jyuch.dev/posts/2025/07-13-2025-spring-nw/&quot;&gt;ネットワークスペシャリスト試験&lt;/a&gt;と&lt;a href=&quot;https://www.jyuch.dev/posts/2025/07-04-saa-c03/&quot;&gt;SAA&lt;/a&gt;の2つを合格・取得できました。&lt;/p&gt;
&lt;p&gt;ネットワークスペシャリスト試験で勉強した内容は普通に日常の業務&lt;s&gt;やテキトーなことを吹っ掛けてくる自称有識者をボコすの&lt;/s&gt;に役に立つので、ネットワークに触る人はみんな勉強すると良いと思います。&lt;/p&gt;
&lt;p&gt;IPAの試験体系が改定されて高度試験が消える前にデータベーススペシャリストもとっておこうかなと思ってます。
セキュリティスペシャリストはどうなるんでしょうね？あれだけ体系が違うっぽいので残る気がしますが。&lt;/p&gt;
&lt;p&gt;AWS系の資格について特にコンプとかは考えていないのですが、SAPを持ってると貰えるお小遣いが増えるので受けてもいいかなと思っています。
まぁ、弊社は全体的に資格難易度と金額が明らかに乖離しているのでにゃーん（社会性フィルタ）&lt;/p&gt;
&lt;p&gt;また、Databricks Certified Data Engineer Associateとかはあまり難しくなさそうなので試しに取ってみたいなとは思ってるのですが、受験料がｸｯｿ高いのと、弊社内でお小遣いが貰える資格ではないのでちょっと迷ってます。&lt;/p&gt;
&lt;h2&gt;コミュニティ&lt;/h2&gt;
&lt;p&gt;東京に勤務することになってから今年はDatabricksのユーザグループであるJEDAIのミートアップから始まり、結構な数の勉強会やもくもく会にお邪魔させていただきました。&lt;/p&gt;
&lt;p&gt;とくに&lt;a href=&quot;https://mito-web-engineer.connpass.com/&quot;&gt;Ibaraki.dev&lt;/a&gt;におかれましては、&lt;a href=&quot;https://speakerdeck.com/jyuch/start-your-own-dns-forwarder-with-hickory-dns&quot;&gt;いきなりDNSの話をし始めたり誰も使ってないDNSのライブラリの話をし始めたり&lt;/a&gt;と大変ご迷惑をお掛けしました。&lt;/p&gt;
&lt;p&gt;いろんな人と交流するとそれぞれの組織の考え方が分かったり、どんな組織でも大体似たようなことで悩んでるんだなというのが分かって楽しいですね。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;2026年も飛躍の年にしたいですね。&lt;/p&gt;
&lt;p&gt;おわり&lt;/p&gt;</content>
	</entry>
</feed>
