<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Storia&apos;s Blog</title><description>技術ブログ</description><link>https://tech.storias-blog.com/</link><language>ja</language><item><title>PySparkとは？ PythonやSQLと何が違うのか実際に比較してみた</title><link>https://tech.storias-blog.com/blogs/py_spark/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/py_spark/</guid><description>PySparkの基本的な仕組みと、Python・SQLite・PySparkでのデータ処理比較、実行計画やパフォーマンスの違いを検証したメモ。</description><pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PySparkという名前は前から耳にしていたが、ぶっちゃけ最初は何者なのかよく分かっていなかった。&lt;/p&gt;
&lt;p&gt;「PySpark」っていうくらいだし、Pythonを使って何かやるライブラリなのだろうとは思っていたが、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;普通のPythonと何が違うんだろう？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という部分がイマイチピンときていなかった。&lt;/p&gt;
&lt;p&gt;さらに調べていると、Spark SQLなんて言葉も登場。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SQLで集計できるなら、わざわざPySpark使わなくてもよくね？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という新たな疑問も浮上。&lt;/p&gt;
&lt;p&gt;もう一つ気になったのが「大量データ処理」の文脈。
なぜデカいデータを扱うときにPySparkがもてはやされるのか。&lt;/p&gt;
&lt;p&gt;「Pythonより単に速いからなのかな？」とも思ったが、考えててもラチがあかないので、今回はWindowsのローカル環境でPySparkを動かしてPythonやSQLite + SQLと比較してみることにした。&lt;/p&gt;
&lt;p&gt;PySparkの基本的な仕組みを調べつつ、同じデータをPython・SQL・PySparkで回してみて、実行計画や処理時間も確認してみる。&lt;/p&gt;
&lt;h2&gt;この記事の対象読者&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PySparkという言葉を初めて聞いた人&lt;/li&gt;
&lt;li&gt;PySparkと普通のPythonの違いがピンときていない人&lt;/li&gt;
&lt;li&gt;SQLとPySparkの違いや使い分けが分からない人&lt;/li&gt;
&lt;li&gt;Sparkが裏側で何をやっているのか知りたい人&lt;/li&gt;
&lt;li&gt;ローカル環境でPySparkをちょっと試してみたい人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;まず結論&lt;/h2&gt;
&lt;p&gt;今回調べて、実際に動かしてみて分かったことを先にまとめておく。&lt;/p&gt;
&lt;h3&gt;結論1：PySparkは「Pythonを高速化したやつ」ではない&lt;/h3&gt;
&lt;p&gt;PySparkは、&lt;strong&gt;Apache SparkのPython API&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;つまり、Pythonそのものを爆速にするライブラリというより、&lt;strong&gt;PythonからSparkのデータ処理エンジンを操作するための入口&lt;/strong&gt;と考えた方がしっくりくる。PySparkは公式でも「Python API for Apache Spark」と説明されていて、PyPIからサクッとインストールしてローカルで試すこともできる。&lt;/p&gt;
&lt;h3&gt;結論2：Spark SQLとPySparkは単純な別物ではない&lt;/h3&gt;
&lt;p&gt;PySparkからSpark SQLを普通に実行できる。&lt;/p&gt;
&lt;p&gt;しかもSpark内部では、SQLで書こうがDataFrame APIで書こうが、最終的な計算にはまったく同じ実行エンジンが使われる仕組みになっている。&lt;/p&gt;
&lt;p&gt;なので、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「SQLかPySparkか、どちらか一方を選ぶ」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;みたいな単純な二者択一の関係ではないっぽい。&lt;/p&gt;
&lt;h3&gt;結論3：PySparkだからといって必ずPythonより速いわけではない&lt;/h3&gt;
&lt;p&gt;今回1,000万行のデータで検証してみたところ、読み込み後の集計処理だけを見れば &lt;code&gt;local[5]&lt;/code&gt; のPySparkが一番速かった。&lt;/p&gt;
&lt;p&gt;ただ、Sparkの起動時間やCSV読み込みまで含めたトータル実行時間だと、今回のローカル環境ではまさかのPython単体の方が速いという結果に。&lt;/p&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「PySparkを使えばPythonより絶対に速くなる」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とは一概には言えない。&lt;/p&gt;
&lt;p&gt;「PySparkってPythonと何が違うの？」という素朴な疑問に対しては、&lt;strong&gt;速度というより処理を実行する仕組みそのものが根本的に違う&lt;/strong&gt;と捉えるのが一番しっくりきた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;そもそもPySparkとは何者？&lt;/h2&gt;
&lt;h3&gt;基本的な定義&lt;/h3&gt;
&lt;p&gt;PySparkは、Apache SparkをPythonから操作するためのAPI。&lt;/p&gt;
&lt;p&gt;Apache Spark自体は、大規模なデータを扱うための分散処理基盤。PySparkを使うことで、PythonからSparkのDataFrameやSQL機能を扱えるようになる。&lt;/p&gt;
&lt;p&gt;イメージ的にはこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Python
   ↓
PySpark
   ↓
Apache Spark
   ↓
データ処理
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここが最初に少し混乱したポイントだった。&lt;/p&gt;
&lt;p&gt;「PySpark」という名前から、最初は「Python製の高速データ処理ライブラリ」みたいなイメージを持っていたが、実際は&lt;strong&gt;PythonからSparkという別物を動かすための窓口&lt;/strong&gt;と考える方がしっくりくる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Sparkの内部はどうなってるの？&lt;/h2&gt;
&lt;p&gt;ここで「じゃあそもそもSparkって中で何やってるの？」という話。&lt;/p&gt;
&lt;p&gt;Sparkアプリケーションは、主に &lt;strong&gt;Driver&lt;/strong&gt; と &lt;strong&gt;Executor&lt;/strong&gt; というプロセス群で動いている。&lt;/p&gt;
&lt;p&gt;公式ドキュメントによると、Driverが &lt;code&gt;SparkContext&lt;/code&gt; を保持して全体を指揮し、Executorが実際にタスクを実行してデータをメモリ上に保持する構成になっている。Driverから各ExecutorへTaskが配られるイメージ。&lt;/p&gt;
&lt;p&gt;構成を図にするとこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    App[&quot;Sparkアプリケーション&quot;]
    Driver[&quot;Driver (SparkContext)&quot;]

    Executor1[&quot;Executor 1&quot;]
    Executor2[&quot;Executor 2&quot;]

    Task1[&quot;Task 1&quot;]
    Task2[&quot;Task 2&quot;]

    Data1[&quot;Data 1&quot;]
    Data2[&quot;Data 2&quot;]

    App --&amp;gt; Driver

    Driver --&amp;gt; Executor1
    Driver --&amp;gt; Executor2

    Executor1 --&amp;gt; Task1
    Executor2 --&amp;gt; Task2

    Task1 --&amp;gt; Data1
    Task2 --&amp;gt; Data2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回の検証は1台のWindows PC上でSparkを動かしている。
なので本格的な複数台マシンによる分散処理ではない。
とはいえ、Sparkの分散処理モデルの雰囲気をローカルで体感することは十分可能。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;local[1]&lt;/code&gt; と &lt;code&gt;local[5]&lt;/code&gt; って何？&lt;/h2&gt;
&lt;p&gt;Sparkをローカル環境で動かすとき、以下のような指定ができる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;local[1]
local[5]
local[*]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spark公式での意味は以下の通り。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local[1]&lt;/code&gt; → 1つのワーカースレッドで動かす&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local[K]&lt;/code&gt; → K個のワーカースレッドで動かす&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local[*]&lt;/code&gt; → マシンの論理コア数いっぱいで動かす&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回の検証PCは20論理プロセッサ搭載だが、CPU負荷を考慮して &lt;code&gt;local[1]&lt;/code&gt; と &lt;code&gt;local[5]&lt;/code&gt; の2パターンで試してみた（&lt;code&gt;local[*]&lt;/code&gt; は今回は見送り）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;なぜにSparkの概念が必要になるのか？&lt;/h2&gt;
&lt;p&gt;ここが一番気になった部分。&lt;/p&gt;
&lt;p&gt;単にデータを集計するだけなら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CSV
 ↓
Python
 ↓
集計
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で済むし、SQLを使うにしても、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CSV
 ↓
SQLite
 ↓
SQL
 ↓
集計
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で十分なはず。&lt;/p&gt;
&lt;p&gt;じゃあ、なんでわざわざSparkなんて大掛かりなものが必要になるのか？&lt;/p&gt;
&lt;p&gt;Spark公式のクラスタ概要を見ると、DriverがTaskを分散し、複数のExecutorで並列処理する前提で作られている。つまり最初から「1つのPythonプロセスで全データを順番に処理する」という世界観ではない。&lt;/p&gt;
&lt;p&gt;なのでSparkの本当の価値は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pythonコードを単に高速化すること&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;というより、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;超巨大なデータを複数のブロックに分解して並列実行できる仕組み&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;にあると考える方が自然っぽい。&lt;/p&gt;
&lt;p&gt;ただし、このあたりの恩恵は1台のPCのローカル検証だけだと全貌は見えにくいかもしれない。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;PySparkのDataFrameって普通のリストやPandasと違うの？&lt;/h2&gt;
&lt;p&gt;PySparkでは、データをDataFrameという単位で扱う。&lt;/p&gt;
&lt;p&gt;コードの書き方自体はこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = (
    df.withColumn(
        &quot;total_sales&quot;,
        col(&quot;price&quot;) * col(&quot;quantity&quot;)
    )
    .groupBy(&quot;category&quot;)
    .agg(
        spark_sum(&quot;total_sales&quot;).alias(&quot;total_sales&quot;)
    )
    .orderBy(&quot;category&quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一見すると、普通のPythonオブジェクトをメソッドチェーンで操作しているように見える。&lt;/p&gt;
&lt;p&gt;が、PySparkのDataFrameはPythonの普通のリストやDictとは別物。&lt;/p&gt;
&lt;p&gt;Spark公式によると、DataFrameは構造化されたデータの「分散コレクション」として定義されている。さらにSpark SQLの最適化エンジン（Catalyst Optimizerなど）が働くことで、計算手順が最適化されてから実行される。&lt;/p&gt;
&lt;p&gt;Pythonの &lt;code&gt;for row in rows:&lt;/code&gt; みたいに1行ずつ真面目に処理していくのとは、根本的に仕組みが違う。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実際にPython・SQL・PySparkを比較してみた&lt;/h2&gt;
&lt;p&gt;というわけで、ここから実際に動かして比較してみる。&lt;/p&gt;
&lt;h3&gt;検証環境&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Windows 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Intel Core Ultra 7 265K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;論理プロセッサ&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メモリ&lt;/td&gt;
&lt;td&gt;約63.35GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;3.13.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Temurin 21.0.12.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PySpark&lt;/td&gt;
&lt;td&gt;4.2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark&lt;/td&gt;
&lt;td&gt;4.2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark実行方式&lt;/td&gt;
&lt;td&gt;&lt;code&gt;local[1]&lt;/code&gt; / &lt;code&gt;local[5]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;※PySpark 4.2.0公式では Python 3.10+、Java 17+ が要件。今回は Python 3.13.15 と Java 21 を使用。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回使用したデータ&lt;/h2&gt;
&lt;p&gt;検証用に、以下のようなシンプルな売上データ（CSV）を用意した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id,product,category,price,quantity
1,Apple,A,100,3
2,Banana,B,200,2
3,Orange,A,150,4
4,Grape,C,300,1
5,Melon,B,500,2
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここから、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;売上金額 = price × quantity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を計算し、&lt;code&gt;category&lt;/code&gt; ごとに合計売上金額を集計する。&lt;/p&gt;
&lt;p&gt;たとえば10行のダミーデータなら、期待する結果は以下。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;total_sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;1700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;2600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;1770&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;検証1：Python単体で集計する&lt;/h2&gt;
&lt;p&gt;まずは一番素直なPython標準ライブラリでの処理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with CSV_FILE.open(newline=&quot;&quot;, encoding=&quot;utf-8&quot;) as f:
    reader = csv.DictReader(f)

    for row in reader:
        category = row[&quot;category&quot;]
        price = int(row[&quot;price&quot;])
        quantity = int(row[&quot;quantity&quot;])

        totals[category] += price * quantity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PythonがCSVを開き、1行ずつDictにしてループで集計していくスタイル。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CSV
 ↓
Python
 ↓
1行ずつル〜プ処理
 ↓
集計
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出力結果は当然こうなる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 1700
B 2600
C 1770
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;検証2：SQLite + SQLで集計する&lt;/h2&gt;
&lt;p&gt;次はSQL。&lt;/p&gt;
&lt;p&gt;Python組み込みの &lt;code&gt;sqlite3&lt;/code&gt; を使ってメモリ上（&lt;code&gt;&quot;:memory:&quot;&lt;/code&gt;）にテーブルを作って集計してみた。&lt;/p&gt;
&lt;p&gt;SQL文は標準的なやつ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    category,
    SUM(price * quantity) AS total_sales
FROM sales
GROUP BY category
ORDER BY category
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果は当然同じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 1700
B 2600
C 1770
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pythonのループ処理と比べると、コードの書き味はかなり変わる。&lt;/p&gt;
&lt;h3&gt;Pythonでの書き方&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;for row in reader:
    totals[category] += price * quantity
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SQLでの書き方&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    category,
    SUM(price * quantity)
FROM sales
GROUP BY category
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初は「SQLとPySparkってライバル関係なのかな？」と思ってたが、ここで少し視点が変わってきた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証3：PySpark（DataFrame API）で集計する&lt;/h2&gt;
&lt;p&gt;PySparkでの書き方はこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = (
    df.withColumn(
        &quot;total_sales&quot;,
        col(&quot;price&quot;) * col(&quot;quantity&quot;)
    )
    .groupBy(&quot;category&quot;)
    .agg(
        spark_sum(&quot;total_sales&quot;).alias(&quot;total_sales&quot;)
    )
    .orderBy(&quot;category&quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行結果は勿論これ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 1700
B 2600
C 1770
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果自体は一緒だが、大事なのは「裏で何が起きているか」。&lt;/p&gt;
&lt;p&gt;PythonはPythonのインタプリタが1行ずつ律儀に回しているが、PySparkの方は「こういう処理をしてね」というプランをSparkエンジン側に組み立ててから実行させている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証4：PySparkの中でSQLを使う&lt;/h2&gt;
&lt;p&gt;ここで「SQLじゃだめなの？」という疑問を深掘りしてみる。&lt;/p&gt;
&lt;p&gt;実はPySparkでは、DataFrameを一時ビュー（TempView）として登録できる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df.createOrReplaceTempView(&quot;sales&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登録しちゃえば、普通にSQLが書ける。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = spark.sql(&quot;&quot;&quot;
    SELECT
        category,
        SUM(price * quantity) AS total_sales
    FROM sales
    GROUP BY category
    ORDER BY category
&quot;&quot;&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;つまり構造としてはこう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PySpark
 ├─ DataFrame API (メソッドチェーンで書く)
 │
 └─ Spark SQL (クエリ文字列で書く)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;両方使える。&lt;/p&gt;
&lt;p&gt;Spark公式ドキュメントでも「SQLとDataFrame APIは表現が違うだけで、裏側の計算エンジンは同一」と明言されている。&lt;/p&gt;
&lt;p&gt;なので、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「SQLかPySparkか」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という比較ではなく、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「Sparkの上で、SQLとDataFrame APIのどっちの書き方で指示を出すか」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という話だったことが分かった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証5：実行計画を見てみる&lt;/h2&gt;
&lt;p&gt;せっかくなので、Sparkの裏側（実行計画）を覗いてみる。&lt;/p&gt;
&lt;p&gt;PySparkには &lt;code&gt;explain()&lt;/code&gt; メソッドがある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result.explain(mode=&quot;formatted&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spark SQLで書いた場合の実行計画を出してみると、こんな感じのフローになっていた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scan csv
 ↓
HashAggregate
 ↓
Exchange
 ↓
HashAggregate
 ↓
Exchange
 ↓
Sort
 ↓
AdaptiveSparkPlan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DataFrame APIで書いた場合もほぼ同じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scan csv
 ↓
Project
 ↓
HashAggregate
 ↓
Exchange
 ↓
HashAggregate
 ↓
Exchange
 ↓
Sort
 ↓
AdaptiveSparkPlan
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;Scan csv&lt;/code&gt; って何？&lt;/h3&gt;
&lt;p&gt;CSVファイルを読み取っているステップ。&lt;code&gt;category&lt;/code&gt; &lt;code&gt;price&lt;/code&gt; &lt;code&gt;quantity&lt;/code&gt; といった必要な列をスキャンしている。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Project&lt;/code&gt; って何？&lt;/h3&gt;
&lt;p&gt;DataFrame APIで指定した &lt;code&gt;.withColumn(&quot;total_sales&quot;, col(&quot;price&quot;) * col(&quot;quantity&quot;))&lt;/code&gt; の部分。
&lt;code&gt;price * quantity&lt;/code&gt; の計算を行って &lt;code&gt;total_sales&lt;/code&gt; という列を作るステップが &lt;code&gt;Project&lt;/code&gt; として現れている。&lt;/p&gt;
&lt;p&gt;SQL版だと &lt;code&gt;SUM(price * quantity)&lt;/code&gt; の中に直接書いたため独立した &lt;code&gt;Project&lt;/code&gt; ノードとしては見えなかったっぽい。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Exchange&lt;/code&gt; って何？（ちょっと気になったポイント）&lt;/h3&gt;
&lt;p&gt;一番目についたのが &lt;code&gt;Exchange&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;詳細を見ると &lt;code&gt;hashpartitioning(category, 200)&lt;/code&gt; みたいな記述がある。
&lt;code&gt;GROUP BY category&lt;/code&gt; を処理するために、データをカテゴリごとにパーティション再配置（シャッフル）しているということ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;元のデータ
   ↓
categoryを基準にデータを再配置（Exchange）
   ↓
集計（HashAggregate）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;単にファイルを上から読んで加算しているわけではなく、分散処理に適した形でデータを並び替えてから集計しているのが見て取れる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;性能も比較してみた&lt;/h2&gt;
&lt;p&gt;ここまでの仕組みを踏まえて、「で、実際に処理速度はどれくらい違うの？」を測ってみた。&lt;/p&gt;
&lt;p&gt;※測定条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 11 / 同一PC / 同一CSV / 同一集計処理&lt;/li&gt;
&lt;li&gt;PySparkは &lt;code&gt;local[1]&lt;/code&gt; と &lt;code&gt;local[5]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意点として、PythonとPySparkではメモリ構造も動作原理も違うため、あくまで「今回の条件での測定値メモ」として見てほしい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1,000万行のデカいCSVで勝負&lt;/h2&gt;
&lt;p&gt;1,000万行のテスト用CSVを作成。
Python側はCSV読み込み完了後の「純粋な集計処理（5回測定）」のタイムを計測。&lt;/p&gt;
&lt;h3&gt;Python単体&lt;/h3&gt;
&lt;p&gt;測定値（秒）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.392956&lt;/li&gt;
&lt;li&gt;0.389598&lt;/li&gt;
&lt;li&gt;0.380789&lt;/li&gt;
&lt;li&gt;0.408727&lt;/li&gt;
&lt;li&gt;0.390020&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;平均：&lt;strong&gt;0.392418秒&lt;/strong&gt; / 中央値：&lt;strong&gt;0.390020秒&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;PySpark &lt;code&gt;local[1]&lt;/code&gt; (スレッド数: 1)&lt;/h3&gt;
&lt;p&gt;ウォームアップ実行後、5回測定。&lt;/p&gt;
&lt;p&gt;測定値（秒）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.602338&lt;/li&gt;
&lt;li&gt;0.589854&lt;/li&gt;
&lt;li&gt;0.591355&lt;/li&gt;
&lt;li&gt;0.588073&lt;/li&gt;
&lt;li&gt;0.573362&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;平均：&lt;strong&gt;0.588996秒&lt;/strong&gt; / 中央値：&lt;strong&gt;0.589854秒&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;PySpark &lt;code&gt;local[5]&lt;/code&gt; (スレッド数: 5)&lt;/h3&gt;
&lt;p&gt;測定値（秒）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.294400&lt;/li&gt;
&lt;li&gt;0.247066&lt;/li&gt;
&lt;li&gt;0.238032&lt;/li&gt;
&lt;li&gt;0.230247&lt;/li&gt;
&lt;li&gt;0.231689&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;平均：&lt;strong&gt;0.248287秒&lt;/strong&gt; / 中央値：&lt;strong&gt;0.238032秒&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;性能結果を並べてみる&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;平均&lt;/th&gt;
&lt;th&gt;中央値&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;0.392418秒&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.390020秒&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PySpark &lt;code&gt;local[1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.588996秒&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.589854秒&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PySpark &lt;code&gt;local[5]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.248287秒&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.238032秒&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;中央値で比較すると、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PySpark local[5]  → 0.238秒 (最速)
Python            → 0.390秒
PySpark local[1]  → 0.590秒
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;マルチスレッドな &lt;code&gt;local[5]&lt;/code&gt; にすると、Python単体より約1.64倍速くなった。
一方でシングルスレッドの &lt;code&gt;local[1]&lt;/code&gt; だと、オーバーヘッドのせいかPython単体より遅いという結果に。&lt;/p&gt;
&lt;p&gt;ここからも、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「PySparkにすれば無条件でPythonより速くなる」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;わけではなく、&lt;strong&gt;並列度を効かせることで初めて集計処理が速くなる&lt;/strong&gt;ということが良くわかる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実行時間全体（トータルタイム）でも比較してみた&lt;/h2&gt;
&lt;p&gt;今度は「CSVの読み込み」や「Spark/JVMの起動コスト」も含めた全体の時間を測定してみた。&lt;/p&gt;
&lt;p&gt;1,000万行データでの結果：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;実行時間&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.065秒&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PySpark &lt;code&gt;local[1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12.823秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PySpark &lt;code&gt;local[5]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.443秒&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;まさかの&lt;strong&gt;Python単体が一番早い&lt;/strong&gt;という結果に！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;【集計処理だけ】
PySpark local[5] ＞ Python

【起動や読み込みも含めた全体】
Python ＞ PySpark local[5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;見事な逆転劇。&lt;/p&gt;
&lt;p&gt;なぜこうなるかというと、PySparkは背景でJVM（Java仮想マシン）やSparkセッションを立ち上げる初期化コストがかかるため。&lt;/p&gt;
&lt;p&gt;ローカルPCで中規模程度の処理を1回だけサクッと動かすようなケースだと、PySparkの初期化オーバーヘッドが響いてトータルではPython単体の方が早くなる、というオチ。&lt;/p&gt;
&lt;p&gt;「PySpark＝単なるPythonの高速化ツール」という認識がいかにズレているかが実感できる面白い結果となった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;local[1]&lt;/code&gt; と &lt;code&gt;local[5]&lt;/code&gt; の並列度の差&lt;/h2&gt;
&lt;p&gt;データ量によって並列度の差がどう出るかも実験してみた。&lt;/p&gt;
&lt;h3&gt;10万行&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local[1]&lt;/code&gt;：約0.336秒&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local[5]&lt;/code&gt;：約0.337秒
→ データが小さいと差はほぼゼロ。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;100万行&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local[1]&lt;/code&gt;：約0.378秒&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local[5]&lt;/code&gt;：約0.374秒
→ まだ明確な差は出ない。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1,000万行&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local[1]&lt;/code&gt;：&lt;strong&gt;0.590秒&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local[5]&lt;/code&gt;：&lt;strong&gt;0.238秒&lt;/strong&gt;
→ ここで一気に差が開く、、、！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;データ量が小さいうちはオーバーヘッドの方が大きく、1,000万行クラスになって初めてマルチスレッド（&lt;code&gt;local[5]&lt;/code&gt;）の効果がはっきり現れるっぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;調べる前と後で考え方が変わった&lt;/h2&gt;
&lt;h3&gt;調べる前&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;「PySparkってPythonで大量データを爆速処理するライブラリでしょ？」&lt;/li&gt;
&lt;li&gt;「SQLで集計できるならPySparkなんて要らなくね？」&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;調べた後&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PySparkはPythonを直接速くする魔法ではなく、Sparkエンジンを動かすためのAPI。&lt;/li&gt;
&lt;li&gt;裏ではDriver / Executor / Task という分散実行モデルが動いている。&lt;/li&gt;
&lt;li&gt;SQLかPySparkかの二者択一ではなく、Sparkの上で「SQL表記で書くかDataFrame表記で書くか」の違い。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;イメージがガラッと変わった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;「Pythonじゃだめなの？」への回答&lt;/h2&gt;
&lt;p&gt;ローカルPCで小〜中規模のデータをサクッと集計するだけなら、&lt;strong&gt;普通にPython単体で十分なケースが多い。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;実際、今回の1,000万行の全体処理時間はPython単体の方が速かった。&lt;/p&gt;
&lt;p&gt;ただ、データ量が単一マシンのメモリを超えたり、分散クラスタ環境で処理したい場合には、Python単体だと限界が来る。その時にSparkの分散実行モデルをPythonから使えるのがPySparkの強み。&lt;/p&gt;
&lt;p&gt;「Pythonより必ず速いから使う」というより、&lt;strong&gt;「Sparkの分散実行モデルを使いたいから、書きやすいPythonからPySpark経由で触る」&lt;/strong&gt; というのが正しい捉え方っぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;「SQLじゃだめなの？」への回答&lt;/h2&gt;
&lt;p&gt;SQLでも全然OK。今回SQLiteでも同じ集計ができた。&lt;/p&gt;
&lt;p&gt;しかもSpark内部にもSpark SQLがある。&lt;/p&gt;
&lt;p&gt;なので「SQLだと対応できないからPySparkを使う」というわけではない。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Spark
 ├─ Spark SQL（SQLで記述）
 └─ DataFrame API（Python等のコードで記述）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;どっちで書いても裏の計算エンジンは同じなので、チームの好みや処理の複雑さ（前処理のループや条件分岐が多いならDataFrame API、単純集計ならSQLなど）で選べばOK。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;注意点・今回分からなかったこと&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;1台のローカルPCでの検証に過ぎない&lt;/strong&gt;
Spark本来の強みである複数台ノードでの分散環境では試していない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;動作環境依存&lt;/strong&gt;
Windows 11, Core Ultra 7 265K, メモリ64GBでの検証結果なので、環境が変われば数値も変わる。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows環境での警告&lt;/strong&gt;
&lt;code&gt;winutils.exe&lt;/code&gt; や &lt;code&gt;native-hadoop&lt;/code&gt; が無いという警告が出たが、今回のCSV処理自体は正常終了した。実運用環境では要確認。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PySparkはPythonを高速化するライブラリではなく、&lt;strong&gt;Apache SparkをPythonから操作するAPI&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;裏では実行計画が組み立てられ、データが再配置（Exchange）されたりTaskに分割されて動く。&lt;/li&gt;
&lt;li&gt;SQLとPySpark（DataFrame API）は競合ではなく、同じSparkエンジン上の表現違い。&lt;/li&gt;
&lt;li&gt;ローカル環境＆1,000万行程度だとトータル処理時間はPython単体の方が速いこともある。&lt;/li&gt;
&lt;li&gt;大量データや分散処理の仕組みが必要になって初めてPySpark（Spark）の真価が発揮される。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「PySpark＝Pythonの高速版」と思って触ると「あれ、意外と遅くね？」と困惑するが、実行モデルや裏側の仕組みを理解すると使いどころが見えてくる、という話だった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今後調べたいこと&lt;/h2&gt;
&lt;p&gt;今回の検証で、逆に新しい疑問も出てきた。&lt;/p&gt;
&lt;p&gt;特に気になったのが、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Partition
Task
Stage
Exchange
Shuffle
Driver
Executor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;の関係。&lt;/p&gt;
&lt;p&gt;今回 &lt;code&gt;explain()&lt;/code&gt; を見ることで、Sparkが単純なPythonのループ処理ではないことは分かった。&lt;/p&gt;
&lt;p&gt;ただ、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「実際にデータがどのように分割され、Taskとして処理されているのか？」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;までは、まだ十分に理解できていない。&lt;/p&gt;
&lt;p&gt;また、今回はローカルPCの &lt;code&gt;local[1]&lt;/code&gt; / &lt;code&gt;local[5]&lt;/code&gt; までしか試していないため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;複数ノードのSparkクラスタになると何が変わるのか？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;も気になっている。&lt;/p&gt;
&lt;p&gt;このあたりは、今回の記事とは分けて書かないとごちゃごちゃになっちゃいそうなので、別記事で。。。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考資料&lt;/h2&gt;
&lt;p&gt;今回使用したGithubコード群
::github{repo=&quot;ryo50/pyspark-blog-test&quot;}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Apache Spark 4.2.0 PySpark Installation
PySparkのPython対応バージョン、PyPIからのインストール、Java要件など。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache Spark 4.2.0 Spark SQL, DataFrames and Datasets Guide
Spark SQL、DataFrame API、同一実行エンジンについて。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache Spark 4.2.0 Cluster Mode Overview
Driver、Executor、Task、Stageなどの構成について。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache Spark 4.2.0 Submitting Applications
&lt;code&gt;local[K]&lt;/code&gt;、&lt;code&gt;local[*]&lt;/code&gt;、&lt;code&gt;spark-submit&lt;/code&gt;などについて。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PySpark 4.2.0 &lt;code&gt;DataFrame.explain()&lt;/code&gt;
実行計画の確認方法について。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python 3.13 &lt;code&gt;sqlite3&lt;/code&gt; documentation
SQLiteとPython標準の&lt;code&gt;sqlite3&lt;/code&gt;について。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;※今回の記事の検証結果は、2026年8月29日時点の環境で実施したもの。&lt;/strong&gt;
PySparkやJava、Pythonのバージョン、Sparkの仕様は今後変更される可能性があるため、記事公開時には公式ドキュメントも再確認してね。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>スイムレーン図とは？フローチャートやシーケンス図との違いを調べてみた</title><link>https://tech.storias-blog.com/blogs/swimlane/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/swimlane/</guid><description>スイムレーン図とは何か、フローチャートやシーケンス図と何が違うのかを、ログイン処理を題材にMermaidで実際に図を作りながら調べた備忘録。</description><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ユーザーの操作からWeb画面、バックエンドの認証APIまでが絡むログイン機能の設計書を作っているときに、処理の流れを図にする必要があった。&lt;/p&gt;
&lt;p&gt;そのとき、自分はシーケンス図を書こうとしていたのだが、チームの先輩から、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「この内容なら、スイムレーン図の方が合っているかもしれない」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と指摘を受けた。&lt;/p&gt;
&lt;p&gt;スイムレーン図という名前自体は聞いたことがあったが、改めて考えてみると、正直よく分かっていなかった。&lt;/p&gt;
&lt;p&gt;特に気になったのが、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通のフローチャートと何が違うのか&lt;/li&gt;
&lt;li&gt;シーケンス図とは何が違うのか&lt;/li&gt;
&lt;li&gt;UMLの図なのか&lt;/li&gt;
&lt;li&gt;どんな場面で使うのか&lt;/li&gt;
&lt;li&gt;人とシステムを同じ図に書いてよいのか&lt;/li&gt;
&lt;li&gt;APIやDBをレーンにしてもよいのか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という点。&lt;/p&gt;
&lt;p&gt;そこで今回は、スイムレーン図について調べつつ、同じ「ログイン処理」を題材にして、&lt;/p&gt;
&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;p&gt;の3種類をMermaidで実際に作って比較してみる。&lt;/p&gt;
&lt;p&gt;単に「スイムレーン図とは何か」を調べるだけではなく、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;結局、どんなときにどの図を使えばいいのか？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;まで整理することを今回のゴールにする。&lt;/p&gt;
&lt;h3&gt;この記事の対象読者&lt;/h3&gt;
&lt;p&gt;この記事は、以下のような人を対象にしている。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;スイムレーン図という言葉を初めて知った人&lt;/li&gt;
&lt;li&gt;フローチャートとの違いが分からない人&lt;/li&gt;
&lt;li&gt;シーケンス図との違いが分からない人&lt;/li&gt;
&lt;li&gt;UMLとスイムレーン図の関係を知りたい人&lt;/li&gt;
&lt;li&gt;要件定義や設計で、どの図を使えばよいか迷っている人&lt;/li&gt;
&lt;li&gt;Mermaidで設計図を作ってみたい人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;まず結論&lt;/h2&gt;
&lt;p&gt;今回調べてみた結果、自分としては次のように整理した。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;図&lt;/th&gt;
&lt;th&gt;主に表現したいもの&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フローチャート&lt;/td&gt;
&lt;td&gt;処理や判断がどのように進むか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;スイムレーン図&lt;/td&gt;
&lt;td&gt;処理の流れと、その処理を誰・何が担当するか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;シーケンス図&lt;/td&gt;
&lt;td&gt;要素同士がどの順番でやり取りするか&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;どの図が優れているかではなく、「何を伝えたいか」で使い分けが大事&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;というのが今回の結論。&lt;/p&gt;
&lt;p&gt;例えばログイン処理なら、&lt;/p&gt;
&lt;p&gt;「ログイン処理にどんな分岐があるのか」を確認したいならフローチャート。&lt;/p&gt;
&lt;p&gt;「ユーザー、Web、認証APIなどが、それぞれ何を担当するのか」を確認したいならスイムレーン図。&lt;/p&gt;
&lt;p&gt;「Webから認証API、認証APIからDBというように、誰が誰にどの順番で処理を要求するのか」を確認したいならシーケンス図。&lt;/p&gt;
&lt;p&gt;という感じで使い分けられる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;スイムレーン図とは？&lt;/h2&gt;
&lt;h3&gt;基本的な定義&lt;/h3&gt;
&lt;p&gt;スイムレーン図は、処理の流れを表現すると同時に、&lt;strong&gt;その処理を誰・何が担当するのかを区切って表現する図&lt;/strong&gt;のこと。&lt;/p&gt;
&lt;p&gt;「スイムレーン」という名前の通り、図を複数のレーンに分け、それぞれのレーンに担当する処理を配置する。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────┬──────────┬──────────┐
│ ユーザー │ Web画面  │ 認証API   │
├──────────┼──────────┼──────────┤
│          │          │          │
│ 入力     │          │          │
│          │ 認証要求 │          │
│          │          │ 認証     │
│          │ 結果表示 │          │
└──────────┴──────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のような形。&lt;/p&gt;
&lt;p&gt;通常のフローチャートに「誰が担当するのか」という情報を追加したもの、と考えると最初は理解しやすいと思う。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;「スイムレーン」という名前の意味&lt;/h2&gt;
&lt;p&gt;スイムレーンを英語にすると「泳ぐレーン」の意味になる。&lt;/p&gt;
&lt;p&gt;プールのコースを複数のレーンに分けるように、処理を担当者やシステムなどの単位で区切る。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
────────────
Web
────────────
認証API
────────────
DB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という形で区切り、それぞれの場所に処理を配置していく。&lt;/p&gt;
&lt;p&gt;見た目がそのまますぎるが、この形状から「Swimlane」という名前が使われている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;UMLとの関係&lt;/h2&gt;
&lt;p&gt;ここは少し注意が必要だった。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「スイムレーン図というUMLの独立した図がある」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいに考えていた。&lt;/p&gt;
&lt;p&gt;しかし、UMLでは&lt;strong&gt;Activity Partition&lt;/strong&gt;という要素が定義されている。&lt;/p&gt;
&lt;p&gt;Activity Partitionを利用すると、アクティビティ図の中で処理を担当する要素などを区切って表現できる仕組みになっている。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「スイムレーン図＝UMLの独立した図」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と単純に考えるより、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UMLのアクティビティ図では、Activity Partitionを利用してスイムレーン形式で表現できる&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と理解した方が正確っぽい。&lt;/p&gt;
&lt;p&gt;UMLの仕様については、OMGがUML 2.5.1を公開している。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.omg.org/spec/UML/?utm_source=chatgpt.com&quot;&gt;OMG — UML Specification&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;フローチャートじゃだめなの？&lt;/h2&gt;
&lt;p&gt;フローチャートは、基本的に処理や判断の流れを表現するための図。&lt;/p&gt;
&lt;p&gt;例えばログイン処理なら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    Start([開始]) --&amp;gt; Input[ID・パスワードを入力]
    Input --&amp;gt; Auth[認証]
    Auth --&amp;gt; Check{認証成功？}
    Check --&amp;gt;|Yes| Success[ログイン成功]
    Check --&amp;gt;|No| Error[エラー表示]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この図からは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「ログイン処理がどのように進み、どこで分岐するのか」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;がはっきり分かる。&lt;/p&gt;
&lt;p&gt;一方で、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「この処理は誰が担当しているのか」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;までは、この図だけだと見えてこない。&lt;/p&gt;
&lt;p&gt;そこで「誰がやるか」を可視化するためにスイムレーン図を使う形になる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実際にスイムレーン図を作ってみる&lt;/h2&gt;
&lt;h3&gt;検証目的&lt;/h3&gt;
&lt;p&gt;今回の検証では、同じログイン処理をスイムレーン図にして、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;フローチャートと比べて何が分かりやすくなるのか？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;を確認してみる。&lt;/p&gt;
&lt;h3&gt;検証環境&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;作図ツール&lt;/td&gt;
&lt;td&gt;Mermaid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;題材&lt;/td&gt;
&lt;td&gt;ログイン処理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;比較対象&lt;/td&gt;
&lt;td&gt;フローチャート、スイムレーン図、シーケンス図&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;実行環境&lt;/td&gt;
&lt;td&gt;Mermaid対応環境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mermaidのバージョンについては、スイムレーン構文の対応状況が変わる可能性があるため、実際に記事へ掲載する際には使用したバージョンを記録しておく。&lt;/p&gt;
&lt;h3&gt;スイムレーン図&lt;/h3&gt;
&lt;p&gt;Mermaidでは、スイムレーンを使って担当単位を分けることができる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;swimlane-beta LR
 subgraph User[&quot;ユーザー&quot;]
        Input[&quot;ログイン情報を入力&quot;]
        Complete[&quot;ログイン結果を確認&quot;]
    end

    subgraph Web[&quot;Web画面&quot;]
        Request[&quot;認証要求&quot;]
        Result[&quot;認証結果を表示&quot;]
    end

    subgraph Auth[&quot;認証API&quot;]
        Authenticate[&quot;ユーザー認証&quot;]
    end

    Input --&amp;gt; Request
    Request --&amp;gt; Authenticate
    Authenticate --&amp;gt; Result
    Result --&amp;gt; Complete
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この図では、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザー&lt;/li&gt;
&lt;li&gt;Web画面&lt;/li&gt;
&lt;li&gt;認証API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;というレーンを用意している。&lt;/p&gt;
&lt;p&gt;同じログイン処理でも、フローチャートとは違って、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「どの処理を誰・何が担当しているのか」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;が一目で分かりやすくなる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;スイムレーン図で分かったこと&lt;/h2&gt;
&lt;p&gt;実際に同じログイン処理をスイムレーンにしてみると、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;処理の流れそのものが大きく変わったわけではない&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ということが分かった。&lt;/p&gt;
&lt;p&gt;変わったのは、処理に「担当」という視点が加わったこと。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー → ログイン情報入力
Web      → 認証要求
認証API  → 認証処理
Web      → 結果表示
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という関係が見えるようになる。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「普通のフローチャートだとダメ」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ということではない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;確認したい情報が違うだけ&lt;/strong&gt;ということが分かった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;レーンにAPIやDBを置くのはだめなの？&lt;/h2&gt;
&lt;p&gt;ここも気になったので確認してみた。&lt;/p&gt;
&lt;p&gt;結論としては、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;人間（ユーザー）とシステム（Web画面・API・DB）を1つのスイムレーン図に混在させて書いても全く問題ない&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;し、&lt;strong&gt;レーンを人間で分けるかシステムで分けるのも自由&lt;/strong&gt;だと分かった。&lt;/p&gt;
&lt;p&gt;例えば、ユーザーとWeb画面、認証APIを並べれば、「人間の操作からシステムのどのコンポーネントへ処理が引き継がれるか」という境目が非常にクリアに見えるようになる。&lt;/p&gt;
&lt;p&gt;また、システムの処理だけを細かく説明したいなら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
Web
認証API
DB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という区切り方が考えられるし、&lt;/p&gt;
&lt;p&gt;一方、業務フローを説明するのであれば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;顧客
営業
経理
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のような人間の組織単位での区切り方が考えられる。&lt;/p&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「レーンには必ず人間だけ（あるいはシステムだけ）を書かないとダメ」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;というルールはない。&lt;/p&gt;
&lt;p&gt;重要なのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;この図では、何を単位として処理を分けると理解しやすいのか？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という点。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;シーケンス図じゃだめなの？&lt;/h2&gt;
&lt;p&gt;次に、最初に作ろうとしていたシーケンス図と比較してみる。&lt;/p&gt;
&lt;p&gt;シーケンス図では、登場する要素と、それらの間で発生するメッセージの順序を表現できる。&lt;/p&gt;
&lt;p&gt;同じログイン処理をMermaidで表現すると、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    actor User as ユーザー
    participant Web as Web画面
    participant Auth as 認証API
    participant DB as DB

    User-&amp;gt;&amp;gt;Web: ログイン情報を入力
    Web-&amp;gt;&amp;gt;Auth: 認証要求
    Auth-&amp;gt;&amp;gt;DB: ユーザー情報を検索
    DB--&amp;gt;&amp;gt;Auth: ユーザー情報
    Auth--&amp;gt;&amp;gt;Web: 認証結果
    Web--&amp;gt;&amp;gt;User: ログイン結果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;となる。&lt;/p&gt;
&lt;p&gt;こちらでは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ユーザー → Web → 認証API → DB&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という&lt;strong&gt;やり取りの順番やリクエスト・レスポンスの往復&lt;/strong&gt;が分かりやすくなっている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;同じログイン処理を3種類で比較してみる&lt;/h2&gt;
&lt;p&gt;ここまでの結果を整理してみる。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;観点&lt;/th&gt;
&lt;th&gt;フローチャート&lt;/th&gt;
&lt;th&gt;スイムレーン図&lt;/th&gt;
&lt;th&gt;シーケンス図&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;処理の流れ&lt;/td&gt;
&lt;td&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;分岐&lt;/td&gt;
&lt;td&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;担当者・担当システム&lt;/td&gt;
&lt;td&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;時系列&lt;/td&gt;
&lt;td&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;システム間のやり取り&lt;/td&gt;
&lt;td&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;業務の全体像&lt;/td&gt;
&lt;td&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;APIなどの詳細な連携&lt;/td&gt;
&lt;td&gt;△&lt;/td&gt;
&lt;td&gt;○&lt;/td&gt;
&lt;td&gt;◎&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;この表からも分かるように、単純に「スイムレーン図がフローチャートより優れている」という話ではない。&lt;/p&gt;
&lt;p&gt;それぞれ得意・不得意がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;どの図を使えばいいのか問題&lt;/h2&gt;
&lt;p&gt;今回の記事で一番整理したかった部分。&lt;/p&gt;
&lt;p&gt;自分は次のように考えると選びやすいと思った。&lt;/p&gt;
&lt;h3&gt;「処理がどう進むのか」を知りたい&lt;/h3&gt;
&lt;p&gt;まずフローチャートを検討する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;処理A
 ↓
処理B
 ↓
判断
 ├→ 処理C
 └→ 処理D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;処理の流れや分岐を確認することが目的なら、シンプルなフローチャートで十分な場合が多い。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;「誰・何が担当するのか」も知りたい&lt;/h3&gt;
&lt;p&gt;スイムレーン図を候補にする。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
Web
API
DB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のように分け、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「この処理は誰が担当しているのか？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;を確認する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;「誰が誰に、どの順番で処理を要求するのか」を知りたい&lt;/h3&gt;
&lt;p&gt;シーケンス図を候補にする。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A → B
B → C
C → B
B → A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のようなメッセージの流れを確認できる。&lt;/p&gt;
&lt;p&gt;特に、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API呼び出し&lt;/li&gt;
&lt;li&gt;システム間連携&lt;/li&gt;
&lt;li&gt;リクエスト&lt;/li&gt;
&lt;li&gt;レスポンス&lt;/li&gt;
&lt;li&gt;呼び出し順序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などを詳しく確認したい場合に向いている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;誰が読む資料なのかも考える&lt;/h2&gt;
&lt;p&gt;今回調べていて、もう一つ重要だと思ったことがある。&lt;/p&gt;
&lt;p&gt;それは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「誰が読む資料なのか？」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という点。&lt;/p&gt;
&lt;p&gt;同じログイン処理でも、読む人によって必要な情報は変わる。&lt;/p&gt;
&lt;p&gt;例えば業務担当者に対して、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「認証APIがDBへSQLを発行して……」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と説明しても、必要以上に細かいかもしれない。&lt;/p&gt;
&lt;p&gt;逆に開発者に対して、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「ユーザーがログインすると認証されます」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;だけでは情報不足になる。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;何を伝えたいのか&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;だけではなく、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;誰に伝えるのか&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;も図を選ぶときの重要なポイントだと感じた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;要件定義では、どこまで細かく書くのか？&lt;/h2&gt;
&lt;p&gt;今回、自分がシーケンス図を書こうとしたとき、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「まだ要件定義の段階なので、細かいものはいらない」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と言われた。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「シーケンス図ではなくスイムレーン図の方が正しいのかな？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と考えていた。&lt;/p&gt;
&lt;p&gt;しかし今回調べてみると、必ずしもそういう意味ではなさそう。&lt;/p&gt;
&lt;p&gt;重要なのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;その段階で何を共有・確認する必要があるのか&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という点。&lt;/p&gt;
&lt;p&gt;例えば要件定義の初期段階なら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
 ↓
ログイン
 ↓
認証
 ↓
ログイン完了
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;程度の全体像で十分かもしれない。&lt;/p&gt;
&lt;p&gt;一方、詳細設計では、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Web
 ↓
認証API
 ↓
DB
 ↓
認証API
 ↓
Web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という具体的な処理順序を確認する必要が出てくる場合がある。&lt;/p&gt;
&lt;p&gt;この場合は、シーケンス図の方が適している。&lt;/p&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;図の種類だけではなく、設計の段階に応じて必要な情報量も変わる&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と考えた方が良さそう。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;スイムレーン図のメリット・デメリット&lt;/h2&gt;
&lt;h3&gt;メリット&lt;/h3&gt;
&lt;p&gt;最大のメリットは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;処理の流れと担当範囲を一緒に確認できること&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;これに尽きる。&lt;/p&gt;
&lt;p&gt;特に、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;人からシステムへ処理が移る&lt;/li&gt;
&lt;li&gt;部署間で処理を引き継ぐ&lt;/li&gt;
&lt;li&gt;複数システムが関係する&lt;/li&gt;
&lt;li&gt;担当範囲を明確にしたい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といった場合に、処理の流れだけでは見えにくい責任範囲を整理できる。&lt;/p&gt;
&lt;h3&gt;デメリット&lt;/h3&gt;
&lt;p&gt;一方で、レーンを増やしすぎると図が複雑になる問題がある。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
Web
SPA
API Gateway
認証API
認証サービス
DB
Cache
外部認証サービス
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;まで細かく分けてしまうと、何を伝えるための図なのか分からなくなる。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;必要な情報を表現するために必要なレーンだけ用意する&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;のが大事。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;調べる前と調べた後&lt;/h2&gt;
&lt;h3&gt;調べる前&lt;/h3&gt;
&lt;p&gt;自分は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「スイムレーン図って、フローチャートに担当者を書いただけなのでは？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいに考えていた。&lt;/p&gt;
&lt;p&gt;また、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「シーケンス図とは何が違うんだろう？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という疑問もあった。&lt;/p&gt;
&lt;h3&gt;調べた後&lt;/h3&gt;
&lt;p&gt;調べてみると、重要なのは図の名前よりも、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「何を伝えたいのか？」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ということだと分かった。&lt;/p&gt;
&lt;p&gt;フローチャートなら処理の流れ。&lt;/p&gt;
&lt;p&gt;スイムレーン図なら処理の流れと担当。&lt;/p&gt;
&lt;p&gt;シーケンス図なら要素間のやり取りと順序。&lt;/p&gt;
&lt;p&gt;というように、同じログイン処理でも、見るポイントを変えられる。&lt;/p&gt;
&lt;p&gt;また、スイムレーン図についても、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「UMLのスイムレーン図という独立した図がある」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と考えていたが、UMLではActivity Partitionを利用してスイムレーン形式の表現ができる、という関係を知ることができた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;注意点・今回分からなかったこと&lt;/h2&gt;
&lt;p&gt;今回の比較では、同じログイン処理を3種類の図で表現した。&lt;/p&gt;
&lt;p&gt;ただし、実際のプロジェクトでは、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「要件定義では必ずこの図」&lt;/li&gt;
&lt;li&gt;「詳細設計では必ずこの図」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という共通ルールがあるとは限らない。&lt;/p&gt;
&lt;p&gt;また、「スイムレーン図」という言葉も、UMLだけでなく業務プロセスやBPMNなど、複数の文脈で使用される。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「スイムレーン図は必ずこの書き方」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と決めつけるのは避けた方が良さそう。&lt;/p&gt;
&lt;p&gt;今回の検証も、あくまで&lt;strong&gt;同じログイン処理を異なる表現で描くことで、それぞれ何が見えやすくなるのかを比較したもの&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;実際の設計では、プロジェクトで採用している記法やルール、資料の読者などを考えて判断する必要がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回は、設計書を作っているときに、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「シーケンス図よりスイムレーン図の方が合っているかもしれない」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と言われたことをきっかけに、スイムレーン図について調べてみた。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「フローチャートと何が違うの？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という程度の理解だった。&lt;/p&gt;
&lt;p&gt;そこで、同じログイン処理を、&lt;/p&gt;
&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;p&gt;の3種類で実際に作って比較してみた。&lt;/p&gt;
&lt;p&gt;その結果、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;フローチャート&lt;/strong&gt; → 処理や判断の流れを見る&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;スイムレーン図&lt;/strong&gt; → 処理の流れと担当を見る&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;シーケンス図&lt;/strong&gt; → 要素間のやり取りと順序を見る&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という違いが見えてきた。&lt;/p&gt;
&lt;p&gt;今回の最初の疑問だった、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「スイムレーン図とシーケンス図って、何が違うの？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;については、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;スイムレーン図は「処理と担当の関係」を把握しやすくする表現で、シーケンス図は「要素間のやり取りと順序」を詳しく把握するための表現&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と整理した。&lt;/p&gt;
&lt;p&gt;また、どの図を使うか迷ったときは、&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;何を伝えたいのか&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;誰に伝えるのか&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;どの程度の詳細さが必要なのか&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;を考えると判断しやすそう。&lt;/p&gt;
&lt;p&gt;今回調べたことで、次は「要件定義・基本設計・詳細設計で、図の粒度をどのように変えると分かりやすいのか」もスキルとして持っておいた方がよさそうだけど、どうも苦手なんだよなぁ。。。
どこかで、勉強してまとめます。。。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.omg.org/spec/UML/&quot;&gt;OMG — Unified Modeling Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mermaid.js.org/syntax/swimlanes.html&quot;&gt;Mermaid — Swimlanes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mermaid.js.org/syntax/sequenceDiagram.html&quot;&gt;Mermaid — Sequence Diagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mermaid.js.org/syntax/flowchart.html&quot;&gt;Mermaid — Flowchart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.microsoft.com/ja-jp/visio/create-a-cross-functional-flowchart&quot;&gt;Microsoft — 部門連係フローチャート&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.iso.org/standard/11955.html&quot;&gt;ISO — ISO 5807:1985&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Databricks Unity CatalogからAWS Glueのテーブルを参照できるか調べてみた</title><link>https://tech.storias-blog.com/blogs/catalog_federation/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/catalog_federation/</guid><description>GlueカタログからDatabricksのUnity Catalogへテーブルをカタログフェデレーションした際の構成や設定手順、遭遇したエラーと対処法について。</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;DatabricksのUnity Catalogを使っているときに、AWS Glueに登録されているテーブルを直接参照できる「カタログフェデレーション」という機能があることを知った。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「GlueのConnectionを作れば、そのままDatabricksから参照できる」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいの認識だった。&lt;/p&gt;
&lt;p&gt;ところが、実際に調べてみると、いくつか気になることが出てきた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;単にGlueへの接続を作るだけで参照できるのか&lt;/li&gt;
&lt;li&gt;IAM Roleの設定はどうすればよいのか&lt;/li&gt;
&lt;li&gt;Service Credential と Storage Credential は何が違うのか&lt;/li&gt;
&lt;li&gt;エラーが発生したときにどう切り分ければよいのか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そこで今回は、実際に検証用のAWS Glue CatalogとS3を用意し、DatabricksからAWS Glueのメタデータを参照して、最終的にS3上のテーブルデータをSQLで取得できるところまで試してみた。&lt;/p&gt;
&lt;h3&gt;この記事の対象読者&lt;/h3&gt;
&lt;p&gt;この記事は、以下のような人を対象にしている。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Databricks Unity Catalog を使っている人&lt;/li&gt;
&lt;li&gt;AWS Glue にある既存テーブルを Databricks から参照したい人&lt;/li&gt;
&lt;li&gt;Service Credential と Storage Credential の違いを知りたい人&lt;/li&gt;
&lt;li&gt;カタログフェデレーション設定時に発生するエラーと対処法を知りたい人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;まず結論&lt;/h2&gt;
&lt;p&gt;今回調べてみて、最初に考えていた、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「GlueのConnectionを作れば簡単に参照できる」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という理解は、少し違っていた。&lt;/p&gt;
&lt;p&gt;実際には、単にConnectionを作るだけではなく、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS IAM Role&lt;/li&gt;
&lt;li&gt;Service Credential&lt;/li&gt;
&lt;li&gt;Storage Credential&lt;/li&gt;
&lt;li&gt;External Location&lt;/li&gt;
&lt;li&gt;Foreign Catalog&lt;/li&gt;
&lt;/ul&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;オブジェクト&lt;/th&gt;
&lt;th&gt;主な役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Credential&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS Glueなどの外部クラウドサービスへのアクセスに使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage Credential&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3などのクラウドストレージへのアクセスに使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;External Location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storage CredentialとS3パスを組み合わせたストレージ領域&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foreign Catalog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Glueなどの外部MetastoreをUnity Catalog上に公開するカタログ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;また、設定時のハマりどころとして、以下のエラーが発生した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AccessDeniedException: not authorized to perform: glue:GetDatabase on resource: .../database/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Databricksは対象のデータベースだけでなく、&lt;code&gt;default&lt;/code&gt; データベースの存在確認も行うため、IAM Policyで &lt;code&gt;database/*&lt;/code&gt; を許可する必要があった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;そもそも何を検証したかったのか？&lt;/h2&gt;
&lt;p&gt;今回確認したかったのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AWS Glueに登録されているテーブルを、Databricks Unity Catalogから参照できるのか？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という点。&lt;/p&gt;
&lt;p&gt;AWS Glue側には、次のようなデータを用意した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AWS Glue Data Catalog
└─ catalog_federation_test
    └─ sample_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そして、テーブルの実データはS3に配置した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/iceberg/sample_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最終的には、このGlueのテーブルをDatabricksから参照する。&lt;/p&gt;
&lt;p&gt;今回構築した全体の検証構成は以下の通り。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Databricks
   │
   │ Unity Catalog
   │
   ├─ Service Credential
   │       │
   │       ▼
   │   AWS IAM Role
   │       │
   │       ├─ AWS Glue
   │       │     └─ catalog_federation_test
   │       │           └─ sample_table
   │       │
   │       └─ Amazon S3
   │             └─ iceberg/sample_table
   │
   ├─ Connection
   │
   ├─ External Location
   │
   └─ Foreign Catalog
          │
          ▼
       sample_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最終的にはDatabricks SQLから以下のデータを取得できた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id    name
1     Alice
2     Bob
3     Carol
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;前提となるAWSリソースの確認と準備&lt;/h2&gt;
&lt;p&gt;まずはAWS側で、今回使用するリソースが存在しているか確認する。&lt;/p&gt;
&lt;h3&gt;1. AWSアカウントを確認する&lt;/h3&gt;
&lt;p&gt;最初にAWS CloudShellから現在使用しているAWSアカウントを確認した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果は以下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;UserId&quot;: &quot;123456789012&quot;,
    &quot;Account&quot;: &quot;123456789012&quot;,
    &quot;Arn&quot;: &quot;arn:aws:iam::123456789012:root&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで、今回使用するAWSアカウントIDが &lt;code&gt;123456789012&lt;/code&gt; であることを確認した。&lt;/p&gt;
&lt;h3&gt;2. AWS GlueにDatabaseとTableが存在することを確認する&lt;/h3&gt;
&lt;p&gt;AWS CloudShellからGlueのDatabaseを確認した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws glue get-databases \
  --region us-east-2 \
  --query &apos;DatabaseList[].Name&apos; \
  --output table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-----------------------------
|       GetDatabases        |
+---------------------------+
|  catalog_federation_test  |
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;続いてTableを確認する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws glue get-tables \
  --region us-east-2 \
  --database-name catalog_federation_test \
  --query &apos;TableList[].Name&apos; \
  --output table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;------------------
|    GetTables   |
+----------------+
|  sample_table  |
+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;さらにCatalog IDも確認した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws glue get-databases \
  --region us-east-2 \
  --query &apos;DatabaseList[].CatalogId&apos; \
  --output table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;------------------
|  GetDatabases  |
+----------------+
|  123456789012  |
+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tableについても確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws glue get-tables \
  --region us-east-2 \
  --database-name catalog_federation_test \
  --query &apos;TableList[].{Name:Name,CatalogId:CatalogId}&apos; \
  --output table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;----------------------------------
|            GetTables           |
+---------------+----------------+
|   CatalogId   |     Name       |
+---------------+----------------+
|  123456789012 |  sample_table  |
+---------------+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これにより、今回参照するGlue Catalogは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Account / Catalog ID
123456789012

Database
catalog_federation_test

Table
sample_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;であることを確認した。&lt;/p&gt;
&lt;h3&gt;3. S3に検証用領域を作る&lt;/h3&gt;
&lt;p&gt;今回は本番データと混ざらないように、検証用S3バケットを用意した。&lt;/p&gt;
&lt;p&gt;バケット：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;catalog-federation-test-20260822-123456789012-us-east-2-an
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;バケット内には、主に以下の領域を用意した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;athena-results/
catalog-metadata/
iceberg/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テーブルデータは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;iceberg/sample_table/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;に配置している。&lt;/p&gt;
&lt;p&gt;最終的に使用するS3パスは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/iceberg/sample_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、Foreign Catalogのメタデータ保存用として、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/catalog-metadata/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を用意した。&lt;/p&gt;
&lt;p&gt;DatabricksのForeign Catalogでは、Icebergテーブルを読む場合、Storage locationとしてメタデータ保存先を指定する必要がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AWS IAM RoleとPolicyの設定&lt;/h2&gt;
&lt;p&gt;DatabricksからAWS GlueおよびS3へアクセスするためのIAM Roleを作成する。&lt;/p&gt;
&lt;h3&gt;1. AWS IAM Roleを作成する&lt;/h3&gt;
&lt;p&gt;Role名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;catalog-federation-test-role
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ARN：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arn:aws:iam::123456789012:role/catalog-federation-test-role
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DatabricksのAWS Glue Federationでは、DatabricksからAWS GlueへアクセスできるIAM Roleを作成し、そのRoleをUnity CatalogのService Credentialから利用する。&lt;/p&gt;
&lt;h3&gt;2. IAM RoleのTrust Policyを設定する&lt;/h3&gt;
&lt;p&gt;DatabricksからRoleをAssumeできるようにTrust Policyを設定した。&lt;/p&gt;
&lt;p&gt;検証時には、Databricksから提示されたTrust Policyを使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;AWS&quot;: [
          &quot;arn:aws:iam::414351767826:role/unity-catalog-prod-UCMasterRole-14S5ZJVKOTYTL&quot;,
          &quot;arn:aws:iam::123456789012:role/catalog-federation-test-role&quot;
        ]
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: {
          &quot;sts:ExternalId&quot;: &quot;your-external-id-uuid&quot;
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで注意したのは、AWSアカウントIDが2種類登場すること。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;123456789012
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;は今回利用したAWSアカウント。&lt;/p&gt;
&lt;p&gt;一方、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;414351767826
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;はTrust Policyで指定されたDatabricks側のPrincipal。&lt;/p&gt;
&lt;p&gt;そのため、GlueのResource ARNには今回のAWSアカウントである&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;123456789012
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を使用する。&lt;/p&gt;
&lt;h3&gt;3. IAM Policyを設定する&lt;/h3&gt;
&lt;p&gt;最初はGlueの対象Databaseだけを許可していた。&lt;/p&gt;
&lt;p&gt;しかし、DatabricksからForeign Catalogを利用すると、&lt;code&gt;default&lt;/code&gt; Databaseの存在確認も行われた。&lt;/p&gt;
&lt;p&gt;そのため、&lt;code&gt;glue:GetDatabase&lt;/code&gt; が &lt;code&gt;arn:aws:glue:us-east-2:123456789012:database/default&lt;/code&gt; に対して拒否された。&lt;/p&gt;
&lt;p&gt;実際のエラーは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;not authorized to perform: glue:GetDatabase
on resource:
arn:aws:glue:us-east-2:123456789012:database/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;だった。&lt;/p&gt;
&lt;p&gt;AWS GlueのData Catalogは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Catalog
 └─ Database
      └─ Table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という階層になっており、AWS公式ドキュメントでもこれらのリソースに対応したARNをIAM Policyで指定できる。&lt;/p&gt;
&lt;p&gt;そこで、今回の検証ではGlueメタデータについてはCatalog全体を読み取り対象とし、S3については検証用バケットだけに限定した。&lt;/p&gt;
&lt;p&gt;最終的に使用したIAM Policyは以下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;GlueReadOnly&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;glue:GetDatabase&quot;,
        &quot;glue:GetDatabases&quot;,
        &quot;glue:GetPartition&quot;,
        &quot;glue:GetPartitions&quot;,
        &quot;glue:GetTable&quot;,
        &quot;glue:GetTables&quot;,
        &quot;glue:GetUserDefinedFunction&quot;,
        &quot;glue:GetUserDefinedFunctions&quot;,
        &quot;glue:BatchGetPartition&quot;
      ],
      &quot;Resource&quot;: [
        &quot;arn:aws:glue:us-east-2:123456789012:catalog&quot;,
        &quot;arn:aws:glue:us-east-2:123456789012:database/*&quot;,
        &quot;arn:aws:glue:us-east-2:123456789012:table/*/*&quot;
      ]
    },
    {
      &quot;Sid&quot;: &quot;S3ListBucket&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:ListBucket&quot;,
        &quot;s3:GetBucketLocation&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::catalog-federation-test-20260822-123456789012-us-east-2-an&quot;
    },
    {
      &quot;Sid&quot;: &quot;S3ReadObjects&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::catalog-federation-test-20260822-123456789012-us-east-2-an/*&quot;
    },
    {
      &quot;Sid&quot;: &quot;S3MetadataWrite&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:PutObject&quot;,
        &quot;s3:DeleteObject&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::catalog-federation-test-20260822-123456789012-us-east-2-an/catalog-metadata/*&quot;
    },
    {
      &quot;Sid&quot;: &quot;S3MetadataMultipart&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:ListBucketMultipartUploads&quot;,
        &quot;s3:ListMultipartUploadParts&quot;,
        &quot;s3:AbortMultipartUpload&quot;
      ],
      &quot;Resource&quot;: &quot;arn:aws:s3:::catalog-federation-test-20260822-123456789012-us-east-2-an&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Glueについては &lt;code&gt;database/*&lt;/code&gt; および &lt;code&gt;table/*/*&lt;/code&gt; としているため、今回の検証では &lt;code&gt;default&lt;/code&gt; Databaseを含むGlue Catalogのメタデータを読み取れる。&lt;/p&gt;
&lt;p&gt;一方、S3については検証用バケットだけに限定している。&lt;/p&gt;
&lt;p&gt;なお、このIAM Policyをそのまま本番環境へ適用することを推奨しているわけではない。本番では、必要なGlue DatabaseやTableに対象を絞るなど、最小権限を検討する必要がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Databricks Unity Catalog側の構築&lt;/h2&gt;
&lt;p&gt;次に、Databricks側で設定を行っていく。&lt;/p&gt;
&lt;h3&gt;1. Service Credentialを作成する&lt;/h3&gt;
&lt;p&gt;まず、Databricks側でService Credentialを作成した。&lt;/p&gt;
&lt;p&gt;Service Credentialには、先ほど作成したAWS IAM Roleを関連付ける。&lt;/p&gt;
&lt;p&gt;名前：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;catalog-federation-test-service-credential
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Databricks公式では、Service Credentialは外部クラウドサービスへのアクセスに使用するUnity Catalogのオブジェクトで、AWSサービスへアクセスする場合はIAM Roleを登録する。&lt;/p&gt;
&lt;p&gt;ここで重要なのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AWS S3のデータアクセス用のStorage Credentialと、AWS Glueのような外部サービスへのアクセスに使うService Credentialは別物&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ということ。&lt;/p&gt;
&lt;h3&gt;2. Storage Credentialを作成する&lt;/h3&gt;
&lt;p&gt;S3のアクセスにはService CredentialではなくStorage Credentialを使用する。&lt;/p&gt;
&lt;p&gt;今回作成したStorage Credential：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;catalog-federation-test-credential
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用途を整理すると、次のようになる。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Credential&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service Credential&lt;/td&gt;
&lt;td&gt;AWS Glueへのアクセス&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage Credential&lt;/td&gt;
&lt;td&gt;S3へのアクセス&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Databricksでは、Storage Credentialがクラウドストレージへの認証情報を表し、External LocationがそのStorage CredentialとS3パスを組み合わせたオブジェクトになる。&lt;/p&gt;
&lt;p&gt;今回の検証では、Storage Credentialの設定が読み取り専用になっていたため、Foreign CatalogのStorage locationとして使用するS3領域について、後から書き込み権限も必要になった。&lt;/p&gt;
&lt;h3&gt;3. External Locationを作成する&lt;/h3&gt;
&lt;p&gt;次にS3のパスをUnity Catalogで管理するため、External Locationを作成した。&lt;/p&gt;
&lt;p&gt;External Locationは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;S3パス
+
Storage Credential
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を組み合わせたUnity Catalogオブジェクト。&lt;/p&gt;
&lt;p&gt;今回の検証では、以下の領域を対象にした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/iceberg/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、Foreign Catalogのメタデータ保存用として、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/catalog-metadata/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もExternal Locationとして用意した。&lt;/p&gt;
&lt;h3&gt;4. File Eventsでエラーが発生した&lt;/h3&gt;
&lt;p&gt;External Locationの作成時、File Eventsのチェックでエラーが発生した。&lt;/p&gt;
&lt;p&gt;表示された内容は、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File Events Permissions Not Verified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で、&lt;code&gt;s3:GetBucketNotification&lt;/code&gt; の権限が不足しているという内容だった。&lt;/p&gt;
&lt;p&gt;今回の検証ではFile Events自体が必須ではなかったため、まずExternal Locationの作成を優先した。&lt;/p&gt;
&lt;p&gt;Databricksの画面でも、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;File events are optional but recommended&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という説明が表示された。&lt;/p&gt;
&lt;p&gt;File Eventsを利用すると、ファイル変更の検知によってストレージの一覧取得を減らせるため、取り込み性能やS3のListコストに影響する可能性がある。ただし、今回の目的はGlue Federationの基本動作確認だったため、File Eventsは検証対象から外した。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/file_events_warning.png&quot; alt=&quot;File Eventsの検証警告画面&quot; /&gt;
&lt;em&gt;▲ External Location作成時に表示される File Events の検証警告画面&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;5. AWS Glue Connectionを作成する&lt;/h3&gt;
&lt;p&gt;次にDatabricksでConnectionを作成した。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connection Type：&lt;code&gt;AWS Glue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;AWS Region：&lt;code&gt;us-east-2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;AWS Account ID：&lt;code&gt;123456789012&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Credential：&lt;code&gt;catalog-federation-test-service-credential&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Databricks公式のGlue Federation手順でも、Connection作成時にAWS Region、AWS Account ID、Service Credentialを指定する流れになっている。&lt;/p&gt;
&lt;h3&gt;6. Connectionのテスト&lt;/h3&gt;
&lt;p&gt;Connection作成前後で接続テストを実施した。&lt;/p&gt;
&lt;p&gt;最終的には、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Success - Assume Role
Success - Self Assume Role
Success - External ID Condition
All Permissions Confirmed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;となった。&lt;/p&gt;
&lt;p&gt;これは、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DatabricksからIAM RoleをAssumeできる&lt;/li&gt;
&lt;li&gt;IAM RoleのSelf Assumeができる&lt;/li&gt;
&lt;li&gt;External ID条件を満たしている&lt;/li&gt;
&lt;li&gt;必要な権限を確認できている&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ことを確認する材料になる。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/connection_test_success.png&quot; alt=&quot;Connectionのテスト成功画面&quot; /&gt;
&lt;em&gt;▲ Databricks Connectionのテスト結果画面&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;7. Foreign Catalogを作成する&lt;/h3&gt;
&lt;p&gt;Connectionが作成できたので、Foreign Catalogを作成する。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Catalog name：&lt;code&gt;catalog-federation-test-connection_catalog&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Connection：&lt;code&gt;catalog-federation-test-connection&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Authorized pathsには、External Locationで許可したS3パスを指定する。&lt;/p&gt;
&lt;p&gt;ここで少し分かりづらかったのが、&lt;code&gt;Authorized paths&lt;/code&gt; と &lt;code&gt;Storage location&lt;/code&gt; の違い。&lt;/p&gt;
&lt;h4&gt;Authorized paths&lt;/h4&gt;
&lt;p&gt;実際のテーブルデータが存在するS3領域。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/iceberg/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Authorized pathsは、Foreign Catalogからアクセスできるクラウドストレージパスを制限するためのもの。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/foreign_catalog_creation_iceberg.png&quot; alt=&quot;Foreign Catalog作成画面&quot; /&gt;
&lt;em&gt;▲ Foreign Catalog作成時の Authorized paths の指定画面&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Storage location&lt;/h4&gt;
&lt;p&gt;Foreign Catalogで使用するメタデータ保存先。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3://catalog-federation-test-20260822-123456789012-us-east-2-an/catalog-metadata/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Icebergテーブルを読む場合、Storage locationの指定が必要になる。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/foreign_catalog_creation_metadata.png&quot; alt=&quot;Foreign Catalog作成画面&quot; /&gt;
&lt;em&gt;▲ Foreign Catalog作成時の Storage location の指定画面&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;8. Foreign Catalogが作成された&lt;/h3&gt;
&lt;p&gt;作成後、&lt;code&gt;catalog-federation-test-connection_catalog&lt;/code&gt; というCatalogが作成された。&lt;/p&gt;
&lt;p&gt;最初はCatalog自体は作成できたものの、配下には &lt;code&gt;No Data&lt;/code&gt; と表示された。&lt;/p&gt;
&lt;p&gt;この時点では、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Catalogが作成できた = テーブルを正常に参照できる&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とは限らない。Foreign Catalogでは、ユーザーやワークフローがアクセスすると外部Metastoreからメタデータが同期される。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実際に動かして遭遇したエラーと切り分け&lt;/h2&gt;
&lt;p&gt;Catalogを作成した後、SQLでテーブルを参照しようとした。&lt;/p&gt;
&lt;h3&gt;1. AWS Glueの権限不足でエラーになった&lt;/h3&gt;
&lt;p&gt;SQLでテーブルを参照しようとすると、以下のエラーが発生した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EXTERNAL_METASTORE_CLIENT_ERROR.OPERATION_FAILED

Unable to verify existence of default database

AccessDeniedException:
not authorized to perform:
glue:GetDatabase

on resource:
arn:aws:glue:us-east-2:123456789012:database/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初に作成したIAM Policyでは、&lt;code&gt;database/catalog_federation_test&lt;/code&gt; だけを許可していた。&lt;/p&gt;
&lt;p&gt;しかしDatabricks側では、&lt;code&gt;database/default&lt;/code&gt; の存在確認も行われていた。&lt;/p&gt;
&lt;p&gt;そのため、&lt;code&gt;catalog_federation_test&lt;/code&gt; に対する権限があっても、&lt;code&gt;default&lt;/code&gt; に対する &lt;code&gt;glue:GetDatabase&lt;/code&gt; が拒否されていた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;エラーログの確認
        ↓
arn:aws:glue:us-east-2:123456789012:database/default が AccessDenied
        ↓
IAM Policyに default データベースの許可がない
        ↓
database/* を許可するように修正
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このエラーから、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IAM RoleのAssume自体は成功しているが、Glueメタデータ取得時のリソース権限が不足している&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と切り分けた。&lt;/p&gt;
&lt;h3&gt;2. IAM Policyを修正する&lt;/h3&gt;
&lt;p&gt;そこでGlueのメタデータ読み取り範囲を、&lt;code&gt;database/*&lt;/code&gt; および &lt;code&gt;table/*/*&lt;/code&gt; まで広げた。&lt;/p&gt;
&lt;p&gt;この結果、DatabricksからGlueのメタデータを取得できるようになり、&lt;code&gt;No Data&lt;/code&gt; だった表示が解消された。&lt;/p&gt;
&lt;p&gt;今回の修正は単に権限を全開放したわけではなく、エラーメッセージに表示された具体的なResource（&lt;code&gt;database/default&lt;/code&gt;）をもとに原因を特定して修正した。&lt;/p&gt;
&lt;h3&gt;3. SQLでテーブルを参照する&lt;/h3&gt;
&lt;p&gt;最後にDatabricks SQLからテーブルを参照した。&lt;/p&gt;
&lt;p&gt;Catalog名には &lt;code&gt;-&lt;/code&gt; が含まれているため、バッククォートで囲む。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM `catalog-federation-test-connection_catalog`.catalog_federation_test.sample_table;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行結果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id    name
1     Alice
2     Bob
3     Carol
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Databricks
    ↓
Unity Catalog
    ↓
Foreign Catalog
    ↓
AWS Glue
    ↓
catalog_federation_test
    ↓
sample_table
    ↓
S3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という経路でデータを取得できることを確認できた。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/sql_query_result.png&quot; alt=&quot;Databricks SQLの実行結果画面&quot; /&gt;
&lt;em&gt;▲ Databricks SQLからAWS Glue経由でS3上のテーブルデータを正常に取得できた最終結果画面&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;カタログUI上からも存在することを確認できた。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/catalog_federation/catalog_ui_result.png&quot; alt=&quot;DatabricksカタログUIの実行結果画面&quot; /&gt;
&lt;em&gt;▲ DatabricksカタログUIでの最終結果画面&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回発生したエラーと原因・対処まとめ&lt;/h2&gt;
&lt;p&gt;今回の検証で発生したエラーと対処をまとめる。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;エラー&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;th&gt;対応&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Credentialが選択できない&lt;/td&gt;
&lt;td&gt;Service Credentialへの権限不足&lt;/td&gt;
&lt;td&gt;Credentialの権限を確認&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Events Permissions Not Verified&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:GetBucketNotification&lt;/code&gt;等が不足&lt;/td&gt;
&lt;td&gt;今回はFile Eventsを検証対象外として作成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Foreign CatalogがNo Data&lt;/td&gt;
&lt;td&gt;Glueメタデータ取得が正常に完了していない&lt;/td&gt;
&lt;td&gt;IAM権限を確認&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glue:GetDatabase&lt;/code&gt; AccessDenied&lt;/td&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt; Databaseへのアクセス権不足&lt;/td&gt;
&lt;td&gt;Glue Databaseの許可範囲を見直し（&lt;code&gt;database/*&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INVALID_IDENTIFIER&lt;/td&gt;
&lt;td&gt;Catalog名に&lt;code&gt;-&lt;/code&gt;が含まれていた&lt;/td&gt;
&lt;td&gt;Catalog名をバッククォートで囲む&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;特に注意したいのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Connectionのテストが成功しても、最終的なSQL実行まで成功するとは限らない&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という点。ConnectionのテストではAssume RoleやExternal IDなどを確認できるが、実際のForeign Catalog利用時にはGlueのメタデータ取得やS3アクセスなど、さらに別の権限が必要になる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実際に調べて分かったこと&lt;/h2&gt;
&lt;h3&gt;調べる前&lt;/h3&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Databricks → AWS Glue&lt;/code&gt; と接続設定（Connection）を作るだけで簡単に参照できる&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいの認識だった。&lt;/p&gt;
&lt;h3&gt;調べた後&lt;/h3&gt;
&lt;p&gt;実際に試してみると、単に接続を作るだけではなく、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS IAM Role&lt;/li&gt;
&lt;li&gt;Service Credential&lt;/li&gt;
&lt;li&gt;Storage Credential&lt;/li&gt;
&lt;li&gt;Connection&lt;/li&gt;
&lt;li&gt;External Location&lt;/li&gt;
&lt;li&gt;Foreign Catalog&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といった複数のオブジェクトが密接に関係していると分かった。&lt;/p&gt;
&lt;p&gt;特に重要だったのが、&lt;strong&gt;Service CredentialとStorage Credentialを分けて考えること&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AWS Glue
    ↑
Service Credential

S3
    ↑
Storage Credential
    ↑
External Location
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という関係で考えると理解しやすい。&lt;/p&gt;
&lt;p&gt;Databricks公式でも、Service CredentialはAWS Glueなどの外部クラウドサービス向け、Storage Credentialはクラウドストレージ向けとして説明されている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;p&gt;今回の検証は、以下の環境で実施している。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Databricks Unity Catalog&lt;/li&gt;
&lt;li&gt;AWS Glue / Amazon S3&lt;/li&gt;
&lt;li&gt;AWS Region: &lt;code&gt;us-east-2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、検証のためにIAM Policyで &lt;code&gt;database/*&lt;/code&gt; を許可しているが、そのまま本番環境へ適用することを推奨するものではない。&lt;/p&gt;
&lt;p&gt;本番環境では、利用するDatabaseやTableに権限を限定できないかを確認し、最小権限を検討する必要がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回調べて分からなかったこと&lt;/h2&gt;
&lt;p&gt;今回の記事では基本機能の疎通確認に絞って検証している。&lt;/p&gt;
&lt;p&gt;そのため、次のような内容は今回の調査範囲外。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File Events を有効にした場合の自動同期・パフォーマンスへの影響&lt;/li&gt;
&lt;li&gt;大規模テーブルでのフェデレーションクエリのレイテンシ&lt;/li&gt;
&lt;li&gt;複数AWSアカウントにまたがるアクセス権限構成&lt;/li&gt;
&lt;li&gt;他のデータフォーマット（Delta Lake, Parquet等）での挙動の違い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWSの知識が圧倒的に不足しているので、権限回りは機会があれば勉強し直したいなぁ
先に資格を取るのが先かな&lt;/p&gt;
&lt;p&gt;パフォーマンスとかは気になるけどたぶんフリートライアルだと限界ありそうなので
誰かがまとめてくれることを願う。。。&lt;/p&gt;
&lt;p&gt;他のデータフォーマット（Delta Lake, Parquet等）での挙動の違いは、、、
正直Glueだけでもすっっっごい時間かかったので、気力あればまとめますん、、、&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回は、Databricks Unity CatalogからAWS Glueのテーブルを参照できるカタログフェデレーションについて調べた。&lt;/p&gt;
&lt;p&gt;最終的な全体構成は以下のようになった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────────────────────────┐
│         Databricks           │
│                              │
│  Unity Catalog               │
│       │                      │
│       ├─ Service Credential  │
│       │                      │
│       ├─ Storage Credential  │
│       │                      │
│       ├─ Connection          │
│       │                      │
│       ├─ External Location   │
│       │                      │
│       └─ Foreign Catalog     │
│              │               │
└──────────────┼───────────────┘
               │
               │ IAM Role / AssumeRole
               ▼
┌──────────────────────────────┐
│            AWS               │
│                              │
│  IAM Role                    │
│  catalog-federation-test-role│
│       │                      │
│       ├──────────────┐       │
│       ▼              ▼       │
│   AWS Glue          S3       │
│       │              │       │
│       ▼              ▼       │
│ catalog_federation_test      │
│       │                      │
│       └─ sample_table ──────┘
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初は「GlueのConnectionを作れば参照できるのでは？」と思っていたが、実際にはS3のExternal LocationやIAM権限、Foreign Catalogの設定など、複数の設定が必要だった。&lt;/p&gt;
&lt;p&gt;また、途中で発生した &lt;code&gt;glue:GetDatabase&lt;/code&gt; のAccessDeniedから、Databricksが対象の &lt;code&gt;catalog_federation_test&lt;/code&gt; だけではなく &lt;code&gt;default&lt;/code&gt; Databaseの存在確認も行っていることが分かった。&lt;/p&gt;
&lt;p&gt;最終的には、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM `catalog-federation-test-connection_catalog`.catalog_federation_test.sample_table;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で、期待通りのデータを取得できた。&lt;/p&gt;
&lt;p&gt;実際の手順を踏んで構築してみることで、IAM、Credential、Connection、External Location、Foreign Catalogというそれぞれの役割と裏側の挙動を確認できた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Databricks: AWS Glue Hive Metastore Federation&lt;/li&gt;
&lt;li&gt;Databricks: Service Credentials&lt;/li&gt;
&lt;li&gt;Databricks: Unity Catalogによるクラウドストレージへの接続&lt;/li&gt;
&lt;li&gt;AWS: AWS Glue Resource ARN&lt;/li&gt;
&lt;li&gt;AWS: AWS Glue GetDatabase API&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Content-Dispositionとは？ファイルダウンロードとファイル名について調べてみた</title><link>https://tech.storias-blog.com/blogs/content_disposition/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/content_disposition/</guid><description>Content-Dispositionヘッダーの仕組みやinlineとattachmentの違い、filename*の指定方法、日本語ファイル名の挙動について解説。</description><pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ファイルダウンロードの仕組みを自作しているときに、&lt;code&gt;Content-Disposition&lt;/code&gt; というHTTPヘッダーが出てきた。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「ファイルをダウンロードさせるために必要なもの」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいの認識だった。&lt;/p&gt;
&lt;p&gt;ところが、実際に調べてみると、いくつか気になることが出てきた。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; もファイルの種類を表しているのに、なぜ &lt;code&gt;Content-Disposition&lt;/code&gt; も必要なのか？&lt;/p&gt;
&lt;p&gt;また、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; がなくてもファイルを取得できるのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt; と &lt;code&gt;attachment&lt;/code&gt; は何が違うのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; を指定するとChromeではどう動くのか&lt;/li&gt;
&lt;li&gt;日本語のファイル名はどう指定するのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; と &lt;code&gt;filename*&lt;/code&gt; は何が違うのか&lt;/li&gt;
&lt;li&gt;両方指定した場合はどちらが使われるのか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といった疑問も出てきた。&lt;/p&gt;
&lt;p&gt;そこで今回は、Node.jsで簡単なHTTPサーバーを作り、Chromeで実際の挙動を確認しながら調べてみた。&lt;/p&gt;
&lt;h3&gt;この記事の対象読者&lt;/h3&gt;
&lt;p&gt;この記事は、以下のような人を対象にしている。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; という言葉を初めて知った人&lt;/li&gt;
&lt;li&gt;HTTPでファイルダウンロードを実装している人&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Type&lt;/code&gt; と &lt;code&gt;Content-Disposition&lt;/code&gt; の違いが分からない人&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; と &lt;code&gt;filename*&lt;/code&gt; の違いを知りたい人&lt;/li&gt;
&lt;li&gt;ブラウザがダウンロードファイルの名前をどう決めるのか知りたい人&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;まず結論&lt;/h2&gt;
&lt;p&gt;今回調べてみて、最初に考えていた、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「ファイルをダウンロードさせるには &lt;code&gt;Content-Disposition&lt;/code&gt; が必要」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という理解は、少し違っていた。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; は、単純に「ファイルをダウンロードさせるためだけ」のヘッダーではない。&lt;/p&gt;
&lt;p&gt;HTTPレスポンスでは、たとえば次のような指定ができる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: inline
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;inline&lt;/code&gt; はコンテンツを通常の表示方法で扱うことを示し、&lt;code&gt;attachment&lt;/code&gt; はダウンロードする添付ファイルとして扱うことを示す。さらに、&lt;code&gt;filename&lt;/code&gt; や &lt;code&gt;filename*&lt;/code&gt; を指定することで、保存時などに使うファイル名についても情報を伝えられる。RFC 6266では、これらの用途が定義されている。&lt;/p&gt;
&lt;p&gt;一方、&lt;code&gt;Content-Type&lt;/code&gt; は、そのデータがどのようなメディアタイプなのかを示すヘッダー。&lt;/p&gt;
&lt;p&gt;そのため、ざっくり整理すると次のようになる。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ヘッダー&lt;/th&gt;
&lt;th&gt;主な役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;データの種類を示す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Content-Disposition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;レスポンスをどう扱うかを示す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存時などに利用するファイル名を示す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filename*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文字コードを指定した形式でファイル名を示す&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;filename*&lt;/code&gt; は拡張パラメータ形式を使って値の文字コードを指定でき、RFC 8187ではUTF-8をサポートすることが定義されている。&lt;/p&gt;
&lt;p&gt;ただし、仕様としての定義と、実際のブラウザの挙動が常に同じとは限らない。&lt;/p&gt;
&lt;p&gt;そのため、今回はChromeで実際に確認してみる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Content-Dispositionとは？&lt;/h2&gt;
&lt;h3&gt;基本的な定義&lt;/h3&gt;
&lt;p&gt;HTTPのレスポンスにおける &lt;code&gt;Content-Disposition&lt;/code&gt; は、レスポンスのコンテンツをブラウザ内で通常どおり扱うのか、それとも添付ファイルとしてダウンロードするのかといった、コンテンツの扱い方に関する情報を伝えるヘッダー。&lt;/p&gt;
&lt;p&gt;代表的な指定は次の2つ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: inline
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;inline&lt;/code&gt; はブラウザ内で表示可能なコンテンツを通常どおり扱う方向の指定。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;attachment&lt;/code&gt; はダウンロードする添付ファイルとして扱う指定。ブラウザによっては、&lt;code&gt;filename&lt;/code&gt; が指定されていれば、その名前を保存候補として利用する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Content-Typeとの違い&lt;/h2&gt;
&lt;p&gt;今回、最初に気になったのがここ。&lt;/p&gt;
&lt;p&gt;たとえばJPEG画像を返す場合、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;とする。&lt;/p&gt;
&lt;p&gt;これは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「このデータはJPEG画像です」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という情報。&lt;/p&gt;
&lt;p&gt;一方、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「このレスポンスは添付ファイルとして扱います」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という情報。&lt;/p&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type
    ↓
「これは何のデータ？」

Content-Disposition
    ↓
「そのデータをどう扱う？」
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という違いとして考えると分かりやすい。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; はメディアタイプを示し、&lt;code&gt;Content-Disposition&lt;/code&gt; はコンテンツの扱い方を示す。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;なぜContent-Dispositionが必要になったのか？&lt;/h2&gt;
&lt;h3&gt;Content-Typeだけでは「どう扱うか」までは決まらない（Content-Typeだけではダメなの？）&lt;/h3&gt;
&lt;p&gt;たとえば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と返せば、JPEGであることは伝えられる。&lt;/p&gt;
&lt;p&gt;ただし、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「そのJPEGをブラウザに表示するのか、保存させるのか」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という情報は別。&lt;/p&gt;
&lt;p&gt;そこで、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のような指定が使われる。&lt;/p&gt;
&lt;p&gt;今回の検証では、この違いを実際に確認してみる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Content-Dispositionにはどのような指定がある？&lt;/h2&gt;
&lt;p&gt;今回の記事では、主に次の4パターンを確認する。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指定&lt;/th&gt;
&lt;th&gt;今回確認する内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;なし&lt;/td&gt;
&lt;td&gt;ヘッダーを指定しないとどうなるか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;通常の表示として扱われるか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;attachment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロードとして扱われるか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;attachment; filename=...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存時のファイル名が変わるか&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;さらに、ファイル名について、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filename=&quot;sample.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filename*=UTF-8&apos;&apos;...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;の違いも確認する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実際にContent-Dispositionを試してみる&lt;/h2&gt;
&lt;h3&gt;検証環境&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Windows 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;言語&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ランタイム&lt;/td&gt;
&lt;td&gt;Node.js v24.14.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPサーバー&lt;/td&gt;
&lt;td&gt;Node.js標準 &lt;code&gt;http&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ブラウザ&lt;/td&gt;
&lt;td&gt;Google Chrome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;検証ファイル&lt;/td&gt;
&lt;td&gt;TXT / JPEG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Node.jsの &lt;code&gt;response.setHeader()&lt;/code&gt; を使うことで、HTTPレスポンスのヘッダーを設定できる。Node.jsでは、ヘッダー値に不正な文字が含まれている場合、&lt;code&gt;TypeError&lt;/code&gt; が発生する。また、UTF-8文字をヘッダー値として扱う場合にはRFC 8187形式を使用する例がNode.jsのドキュメントにも掲載されている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回確認したこと&lt;/h2&gt;
&lt;p&gt;今回の検証では、以下を確認する。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; がない場合、Chromeはどう扱うのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt; と &lt;code&gt;attachment&lt;/code&gt; では何が変わるのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; を指定すると保存ファイル名が変わるのか&lt;/li&gt;
&lt;li&gt;スペースを含むファイル名はどう扱われるのか&lt;/li&gt;
&lt;li&gt;日本語を &lt;code&gt;filename&lt;/code&gt; に直接指定するとどうなるのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename*&lt;/code&gt; を使うとどうなるのか&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; と &lt;code&gt;filename*&lt;/code&gt; を両方指定するとどちらが使われるのか&lt;/li&gt;
&lt;li&gt;JPEGでも同じような挙動になるのか&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;検証方法&lt;/h2&gt;
&lt;p&gt;今回の検証では、ChromeのDevToolsを利用する。&lt;/p&gt;
&lt;p&gt;基本的に確認するのは次の3つ。&lt;/p&gt;
&lt;h3&gt;1. Network&lt;/h3&gt;
&lt;p&gt;HTTPレスポンスに実際にどのヘッダーが返ってきたか確認する。&lt;/p&gt;
&lt;p&gt;特に、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Response Headers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を確認する。&lt;/p&gt;
&lt;h3&gt;2. Chromeの画面&lt;/h3&gt;
&lt;p&gt;レスポンスをChromeが実際にどう処理したのかを確認する。&lt;/p&gt;
&lt;p&gt;たとえば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ブラウザ内に表示された&lt;/li&gt;
&lt;li&gt;ダウンロードが開始された&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など。&lt;/p&gt;
&lt;h3&gt;3. 保存されたファイル&lt;/h3&gt;
&lt;p&gt;ダウンロードが発生した場合は、実際に保存されたファイル名も確認する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証1：Content-Dispositionを指定しない&lt;/h2&gt;
&lt;p&gt;まずは &lt;code&gt;Content-Disposition&lt;/code&gt; をいっさい指定しないケース。&lt;/p&gt;
&lt;p&gt;今回使用するTXTでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: text/plain
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;だけを返す。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case01-no-disposition-txt&apos;:
  sendText(res);
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sendText()&lt;/code&gt; の中では &lt;code&gt;Content-Type&lt;/code&gt; だけを設定している。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function sendText(res, contentDisposition) {
  res.setHeader(&apos;Content-Type&apos;, &apos;text/plain&apos;);

  if (contentDisposition !== undefined) {
    res.setHeader(&apos;Content-Disposition&apos;, contentDisposition);
  }

  res.end(txtData);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response HeadersにContent-Dispositionがないことも確認。
&lt;img src=&quot;./assets/content_disposition.md/case01-network.png&quot; alt=&quot;検証1&quot; /&gt;
↓
Chromeの画面にテキストが表示された。&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;Chromeでは単なるテキストとしてブラウザ表示されたっぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証2：inline&lt;/h2&gt;
&lt;p&gt;次に、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: inline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を指定してみる。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case02-inline-txt&apos;:
  sendText(res, &apos;inline&apos;);
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;どうなる？&lt;/h3&gt;
&lt;p&gt;仕様上、&lt;code&gt;inline&lt;/code&gt; はコンテンツを通常の表示方法で扱う指定。&lt;/p&gt;
&lt;p&gt;ただし、今回は「仕様上そうなっている」だけで終わらせず、Chromeで実際に確認してみる。&lt;/p&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case02-network.png&quot; alt=&quot;検証2&quot; /&gt;
↓&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: inline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;が付与されているが、Chromeの画面にテキストが表示された。&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;inline&lt;/code&gt; を指定してもブラウザ内に表示されるっぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証3：attachment&lt;/h2&gt;
&lt;p&gt;次に、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を指定してみる。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case03-attachment-txt&apos;:
  sendText(res, &apos;attachment&apos;);
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;attachment&lt;/code&gt; はコンテンツをダウンロードする添付ファイルとして扱うための指定。&lt;/p&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case03-network.png&quot; alt=&quot;検証3&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case03-dialog.png&quot; alt=&quot;検証3&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;case03-attachment-txt.txt&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case03-download.png&quot; alt=&quot;検証3&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証4：filenameを指定する&lt;/h2&gt;
&lt;p&gt;ここからファイル名指定を確認してみる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;sample.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case04-filename-txt&apos;:
  sendText(
    res,
    &apos;attachment; filename=&quot;sample.txt&quot;&apos;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;filename&lt;/code&gt; は、保存時などに利用するファイル名を示すためのパラメータ。&lt;/p&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;sample.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case04-network.png&quot; alt=&quot;検証4&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case04-dialog.png&quot; alt=&quot;検証4&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;sample.txt&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case04-download.png&quot; alt=&quot;検証4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;filename&lt;/code&gt; に指定した名前で、Chromeでも保存された。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証5：スペースを含むfilename&lt;/h2&gt;
&lt;p&gt;次に、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;sample file.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を試してみる。&lt;/p&gt;
&lt;p&gt;ファイル名にスペースなどを含める場合、&lt;code&gt;filename&lt;/code&gt; の値を引用符で囲む形式が利用できる。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case05-filename-space-txt&apos;:
  sendText(
    res,
    &apos;attachment; filename=&quot;sample file.txt&quot;&apos;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;sample file.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case05-network.png&quot; alt=&quot;検証5&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case05-dialog.png&quot; alt=&quot;検証5&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;sample file.txt&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case05-download.png&quot; alt=&quot;検証5&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;filename&lt;/code&gt; にスペースを含む場合は、引用符で囲むことでChromeでも正しくファイル名が保存されるっぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証6：filenameに日本語を直接指定する&lt;/h2&gt;
&lt;p&gt;ここで、次のような日本語ファイル名を指定してみる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;日本語.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case06-filename-japanese-txt&apos;:
  sendText(
    res,
    &apos;attachment; filename=&quot;日本語.txt&quot;&apos;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この検証は、Chromeだけでなく&lt;strong&gt;Node.js側も重要&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Node.jsのHTTP APIでは、ヘッダー値に不正な文字が含まれている場合 &lt;code&gt;TypeError&lt;/code&gt; になる。Node.jsのドキュメントでは、UTF-8文字を含む値を扱う場合にRFC 8187形式を使用する例も示されている。&lt;/p&gt;
&lt;p&gt;Node.js側のターミナルでエラーが発生した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError
ERR_INVALID_CHAR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case06-server.png&quot; alt=&quot;検証6Node.jsエラー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Chrome側はエラーレスポンスが返ってきている。
&lt;img src=&quot;./assets/content_disposition.md/case06-network.png&quot; alt=&quot;検証6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Node.js側でエラーが発生したため、Chrome側でレスポンスを受け取れず500エラーになった。
エラーとしては &lt;code&gt;TypeError&lt;/code&gt; の &lt;code&gt;ERR_INVALID_CHAR&lt;/code&gt; が発生している。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証7：filename*を使う&lt;/h2&gt;
&lt;p&gt;ここで &lt;code&gt;filename*&lt;/code&gt; を試してみる。&lt;/p&gt;
&lt;p&gt;たとえば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;とする。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;filename*&lt;/code&gt; は拡張パラメータ形式を使用し、文字コードを指定した値を表現できる。RFC 8187では、&lt;code&gt;UTF-8&apos;&apos;...&lt;/code&gt; のような形式で文字コードと値を指定する。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case07-filename-star-txt&apos;:
  sendText(
    res,
    &quot;attachment; filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt&quot;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case07-network.png&quot; alt=&quot;検証7&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case07-dialog.png&quot; alt=&quot;検証7&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;日本語.txt&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case07-download.png&quot; alt=&quot;検証7&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;filename*&lt;/code&gt; を使うことで、Chromeでも日本語ファイル名が正しく保存されるようになった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証8：filenameとfilename*を両方指定する&lt;/h2&gt;
&lt;p&gt;次に、2つを同時に指定してみる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment;
 filename=&quot;fallback.txt&quot;;
 filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RFC 6266では、両方が存在する場合、&lt;code&gt;filename*&lt;/code&gt; を理解するユーザーエージェントは &lt;code&gt;filename*&lt;/code&gt; を選択することが推奨されている。古いユーザーエージェントへのフォールバックとして &lt;code&gt;filename&lt;/code&gt; と &lt;code&gt;filename*&lt;/code&gt; の両方を指定する形も示されている。&lt;/p&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case08-filename-both-txt&apos;:
  sendText(
    res,
    &quot;attachment; filename=\&quot;fallback.txt\&quot;; filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt&quot;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Disposition: attachment; filename=&quot;fallback.txt&quot;; filename*=UTF-8&apos;&apos;%E6%97%A5%E6%9C%AC%E8%AA%9E.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case08-network.png&quot; alt=&quot;検証8&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case08-dialog.png&quot; alt=&quot;検証8&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;日本語.txt&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case08-download.png&quot; alt=&quot;検証8&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;仕様との比較&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;仕様上の説明
        ↓
filename* が優先される

実際のChrome
        ↓
filename* の指定 (日本語.txt) で保存された

比較
        ↓
一致した
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;filename&lt;/code&gt; としては &lt;code&gt;&quot;fallback.txt&quot;&lt;/code&gt; にしていたが、&lt;code&gt;filename*&lt;/code&gt; に日本語を指定したため、&lt;code&gt;日本語.txt&lt;/code&gt; の名前で保存された。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証9：inline + filenameをJPEGで確認する&lt;/h2&gt;
&lt;p&gt;ここからJPEGを使ってみる。&lt;/p&gt;
&lt;p&gt;レスポンスを次のように設定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
Content-Disposition: inline; filename=&quot;sample.jpg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case09-inline-filename-jpg&apos;:
  sendJpeg(
    res,
    &apos;inline; filename=&quot;sample.jpg&quot;&apos;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;filename&lt;/code&gt; を指定していても、&lt;code&gt;inline&lt;/code&gt; の場合はどうなるのか？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;を確認する。&lt;/p&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
Content-Disposition: inline; filename=&quot;sample.jpg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case09-network.png&quot; alt=&quot;検証9&quot; /&gt;
↓
保存されたファイル：なし（ダウンロードされず、ブラウザ上で表示された）
&lt;img src=&quot;./assets/content_disposition.md/case09-download.png&quot; alt=&quot;検証9&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;検証結果から分かったこと&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;inline&lt;/code&gt; はダウンロードダイアログが表示されず、ブラウザ画面で表示されるっぽい。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証10：JPEG + attachment&lt;/h2&gt;
&lt;p&gt;最後に、同じJPEGを &lt;code&gt;attachment&lt;/code&gt; にしてみる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
Content-Disposition: attachment; filename=&quot;sample.jpg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証コード&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;case &apos;/case10-attachment-jpg&apos;:
  sendJpeg(
    res,
    &apos;attachment; filename=&quot;sample.jpg&quot;&apos;
  );
  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;p&gt;Response Headersで以下になっていることを確認。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: image/jpeg
Content-Disposition: attachment; filename=&quot;sample.jpg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./assets/content_disposition.md/case10-network.png&quot; alt=&quot;検証10&quot; /&gt;
↓
ダウンロードダイアログが表示されることを確認。
&lt;img src=&quot;./assets/content_disposition.md/case10-dialog.png&quot; alt=&quot;検証10&quot; /&gt;
↓
保存されたファイル名：&lt;code&gt;sample.jpg&lt;/code&gt;
&lt;img src=&quot;./assets/content_disposition.md/case10-download.png&quot; alt=&quot;検証10&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証結果をまとめる&lt;/h2&gt;
&lt;p&gt;10個の検証を実施したら、最後に表で比較する。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;No.&lt;/th&gt;
&lt;th&gt;ファイル&lt;/th&gt;
&lt;th&gt;Content-Disposition&lt;/th&gt;
&lt;th&gt;Chromeの結果&lt;/th&gt;
&lt;th&gt;保存ファイル名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&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;2&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inline&lt;/code&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;3&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;case03-attachment-txt.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename=&quot;sample.txt&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sample.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename=&quot;sample file.txt&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sample file.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename=&quot;日本語.txt&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;エラー (500)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;日本語.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename + filename*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;日本語.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inline; filename=&quot;sample.jpg&quot;&lt;/code&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;10&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attachment; filename=&quot;sample.jpg&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ダウンロード&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sample.jpg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;実際に調べて分かったこと&lt;/h2&gt;
&lt;h3&gt;調べる前&lt;/h3&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; は、ファイルをダウンロードさせるために必要なHTTPヘッダー&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいの認識だった。&lt;/p&gt;
&lt;h3&gt;調べた後&lt;/h3&gt;
&lt;p&gt;調べてみると、&lt;code&gt;Content-Disposition&lt;/code&gt; は「ファイルをダウンロードするためだけ」のものではなく、レスポンスを通常どおり扱うのか、添付ファイルとして扱うのかを示すための仕組みだと分かった。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;filename&lt;/code&gt; や &lt;code&gt;filename*&lt;/code&gt; を利用することで、保存時などに利用するファイル名についても伝えられる。&lt;/p&gt;
&lt;p&gt;一方、&lt;code&gt;Content-Type&lt;/code&gt; はデータのメディアタイプを示すためのもの。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「&lt;code&gt;Content-Type&lt;/code&gt; があるのに、なぜ &lt;code&gt;Content-Disposition&lt;/code&gt; も必要なのか？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という最初の疑問については、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;そもそも役割が違うから&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という理解になった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;p&gt;今回の検証は、以下の環境で実施している。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 11&lt;/li&gt;
&lt;li&gt;Node.js v24.14.1&lt;/li&gt;
&lt;li&gt;Google Chrome&lt;/li&gt;
&lt;li&gt;Node.js標準のHTTPサーバー&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そのため、今回のChromeで確認した結果を、そのまま「すべてのブラウザで同じ」と考えることはできない。&lt;/p&gt;
&lt;p&gt;特にファイル名については、ブラウザや保存先のファイルシステムによって調整される場合がある。MDNでも、ブラウザがファイルシステム上の制約に合わせてファイル名を変換する場合があると説明されている。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;Content-Disposition&lt;/code&gt; がなくてもブラウザがリソースを取得したり保存したりする場合がある。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回調べて分からなかったこと&lt;/h2&gt;
&lt;p&gt;今回の記事ではChromeに絞って検証している。&lt;/p&gt;
&lt;p&gt;そのため、次のような内容は今回の調査範囲外。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FirefoxやSafariでの挙動&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt; を利用したダウンロードとの関係&lt;/li&gt;
&lt;li&gt;複数ブラウザ間での &lt;code&gt;filename&lt;/code&gt; / &lt;code&gt;filename*&lt;/code&gt; の比較&lt;/li&gt;
&lt;li&gt;Windows以外のファイルシステムでのファイル名処理&lt;/li&gt;
&lt;li&gt;長すぎるファイル名の詳細な挙動&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また時間あるときにでも、調査してみようかな、、、&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回は、ファイルダウンロードを自作しているときに気になった &lt;code&gt;Content-Disposition&lt;/code&gt; について調べた。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「ファイルをダウンロードさせるために必要なヘッダー」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という認識だった。&lt;/p&gt;
&lt;p&gt;しかし、調査と検証を通して、より正確には、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Content-Type&lt;/code&gt; はデータの種類を示す&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Disposition&lt;/code&gt; はコンテンツの扱い方を示す&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt; は通常の表示として扱う指定&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attachment&lt;/code&gt; は添付ファイルとして扱う指定&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; で保存時などに利用するファイル名を示せる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename*&lt;/code&gt; で文字コードを指定できる形式でファイル名を示せる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; と &lt;code&gt;filename*&lt;/code&gt; を両方指定する方法もある&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ということが分かった。&lt;/p&gt;
&lt;p&gt;最初に疑問だった、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「&lt;code&gt;Content-Type&lt;/code&gt; があるのに、なぜ &lt;code&gt;Content-Disposition&lt;/code&gt; も必要なの？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;については、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Content-Type&lt;/code&gt; と &lt;code&gt;Content-Disposition&lt;/code&gt; は、そもそも伝えている情報が違うから&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という結論になった。&lt;/p&gt;
&lt;p&gt;また、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「&lt;code&gt;Content-Disposition&lt;/code&gt; がないとファイルをダウンロードできないの？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;については、単純に「必須」と考えるのではなく、&lt;strong&gt;実際のブラウザがレスポンスをどのように扱うかを確認する必要がある&lt;/strong&gt;ということも分かった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考資料&lt;/h2&gt;
&lt;p&gt;今回使用したGithubコード群
::github{repo=&quot;ryo50/content-disposition-test&quot;}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RFC 6266 - Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)&lt;/li&gt;
&lt;li&gt;RFC 8187 - Indicating Character Encoding and Language for HTTP Header Field Parameters&lt;/li&gt;
&lt;li&gt;MDN - Content-Disposition header&lt;/li&gt;
&lt;li&gt;Node.js v24 HTTP API Documentation&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>プロセス間通信（IPC）とは？「REST APIでよくない？」と思ったので調べてみた</title><link>https://tech.storias-blog.com/blogs/inter_process/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/inter_process/</guid><description>Dockerやマイクロサービスを調べる中で気になったプロセス間通信（IPC）について、プロセスの仕組みやHTTP通信との違い、WindowsのNamed Pipeを実際にNode.jsで検証してみました。</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;プロセス間通信（IPC）とは？「REST APIでよくない？」と思ったので調べてみた&lt;/h1&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Dockerについて調べている中で、マイクロサービスに関する話題を見ていたところ、「プロセス間通信（IPC）」という言葉が出てきた。&lt;br /&gt;
また、以前システム開発をしていたときにも、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;プロセス間通信の方がセキュリティ面や速度面で有効&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という話を聞いたことがある。&lt;br /&gt;
ただ、当時は「プロセス間通信」という言葉自体を深く理解していなかった。&lt;br /&gt;
特に気になったのが、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP通信と何が違うの？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という部分だった。&lt;br /&gt;
REST APIならHTTPを使ってデータをやり取りできる。&lt;br /&gt;
実装方法も分かりやすく、利用しているシステムも多い。&lt;br /&gt;
それなら、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;分かりづらいプロセス間通信をわざわざ使うメリットがそこまで多くないなら、REST APIでよくない？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と思った。&lt;br /&gt;
さらに調べていくと、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;そもそもプロセスとは何なのか&lt;/li&gt;
&lt;li&gt;プログラムとプロセスは何が違うのか&lt;/li&gt;
&lt;li&gt;プロセス間通信では何を使っているのか&lt;/li&gt;
&lt;li&gt;HTTP通信もプロセス間通信と言えるのではないか&lt;/li&gt;
&lt;li&gt;ソケットとプロセス間通信は何が違うのか&lt;/li&gt;
&lt;li&gt;パイプとは何なのか&lt;/li&gt;
&lt;li&gt;共有メモリとは何なのか&lt;/li&gt;
&lt;li&gt;なぜIPCは速度面やセキュリティ面で有効と言われるのか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、いろいろな疑問が出てきた。&lt;/p&gt;
&lt;p&gt;そこで今回はDockerの検証はいったん切り離し、まずは&lt;strong&gt;プロセス間通信（IPC）そのもの&lt;/strong&gt;について調べてみる。&lt;/p&gt;
&lt;p&gt;実際の検証では、Windows環境にNode.jsをインストールし、HTTP通信とWindowsのNamed Pipeを使った通信を比較する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;まず結論&lt;/h2&gt;
&lt;p&gt;今回の調査では、プロセス間通信（IPC）について、次のように整理できました。&lt;/p&gt;
&lt;p&gt;HTTPは、&lt;code&gt;127.0.0.1:3000&lt;/code&gt;のようなIPアドレスとTCPポートを使用し、HTTPプロトコルによって別プロセス間で通信する。&lt;br /&gt;
WindowsのNamed Pipeは、&lt;code&gt;\\.\pipe\ipc-test&lt;/code&gt; のようなPipe名を使用し、TCP/IPやTCPポートを介さずに別プロセス間で通信できる。&lt;br /&gt;
今回の環境で10,000回の通信を実測したところ、Named PipeはHTTPより大幅に短い時間で処理できた。&lt;br /&gt;
HTTPは合計&lt;strong&gt;189,208.053ms&lt;/strong&gt;（平均18.921ms）、Named Pipeは合計&lt;strong&gt;4,964.665ms&lt;/strong&gt;（平均0.496ms）だった。&lt;br /&gt;
環境は&lt;strong&gt;Windows11 + CPU Intel(R) Core(TM) Ultra 7 265K + Node.js v24.14.1&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;今回の測定では、Named Pipeの合計処理時間はHTTPの約2.63%、つまりHTTPより約97.4%短い時間で処理された。&lt;/p&gt;
&lt;p&gt;ただし、これはあくまでWindows + Node.js v24.14.1 + 今回作成した実装・実行環境における測定結果。&lt;br /&gt;
通信方式の違いによる傾向を確認するための検証であり、すべての環境で同じ速度差になることを示すものではない。&lt;br /&gt;
そのため、この結果だけから「Named Pipeは常にHTTPより約38倍速い」と一般化することはできない。&lt;/p&gt;
&lt;p&gt;また、最初に疑問に思っていた、&lt;/p&gt;
&lt;p&gt;「REST APIでよくない？」&lt;/p&gt;
&lt;p&gt;については、「同じPC上のプロセス間で高速な通信を行うこと」が目的なら、必ずしもREST APIを選ぶ必要はないという結論になった。
HTTP/REST APIは、ネットワーク越しの通信や、異なる&lt;strong&gt;システム・言語・サービス間で通信する&lt;/strong&gt;場合には扱いやすく、汎用性が高いというメリットがある。&lt;/p&gt;
&lt;p&gt;一方、通信相手が同じWindowsマシン上のプロセスに限定されているのであれば、Named PipeのようなOSが提供するIPCを利用することで、HTTP/TCPを使わずにプロセス間通信を行える。今回の実測でも、HTTPより短い時間で通信を処理できた。&lt;/p&gt;
&lt;p&gt;また、セキュリティ面では、Named Pipeへの接続にWindowsのユーザー権限が影響することも確認できた。&lt;br /&gt;
今回の検証では、Serverと同じ mainaccount からは接続できた一方、別ユーザーの ipc-test-user からは EPERM となり接続できなかった。&lt;br /&gt;
Node.js側で独自の認証処理を実装していなくても、Windows側のアクセス制御によって接続可否が判断されることを確認できた。&lt;/p&gt;
&lt;p&gt;そのため、Named Pipeは単に「ローカル通信だから安全」と考えるのではなく、Windowsのユーザー権限やNamed Pipeのアクセス制御を考慮して利用する必要がある。&lt;/p&gt;
&lt;p&gt;つまり、
「REST APIでよいか」は通信相手と目的次第であり、同一マシン内のプロセス間通信だけが目的なら、Named PipeなどのIPCも有力な選択肢になる。&lt;br /&gt;
というのが、今回の検証から得られた結論。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;プロセスとは？&lt;/h1&gt;
&lt;p&gt;プロセス間通信について調べる前に、そもそも「プロセス」とは何なのかを整理する。&lt;br /&gt;
自分の場合、調べる前は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;exeファイルなどのプログラムを実行したものがプロセス？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;くらいの理解だった。&lt;br /&gt;
大きく間違ってはいないものの、「プログラム」と「プロセス」を同じものとして考えていた部分がある。&lt;/p&gt;
&lt;h2&gt;プログラムとプロセスの違い&lt;/h2&gt;
&lt;p&gt;プログラムは、コンピューター上で実行される命令やコードそのものを指す。&lt;br /&gt;
一方、プロセスは、プログラムが実行されている状態をOSが管理しているもの。&lt;/p&gt;
&lt;p&gt;イメージとしては、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プログラム
   │
   │ 実行
   ▼
プロセス
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;となる。&lt;br /&gt;
同じプログラムであっても、複数回起動すれば複数のプロセスとして動作する場合がある。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;example.exe
    │
    ├─ 実行 → プロセスA
    │
    └─ 実行 → プロセスB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のような状態になる。&lt;br /&gt;
つまり、「プログラム」と「プロセス」は同じものではない。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;プロセスとスレッドの違い&lt;/h1&gt;
&lt;p&gt;スレッドについては、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;スレッドはプロセスの中で動くもの&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という程度の理解だった。&lt;br /&gt;
基本的にはこの理解でよく、1つのプロセスの中で複数のスレッドが動作することがある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プロセス
├─ スレッドA
├─ スレッドB
└─ スレッドC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロセスとスレッドの大きな違いとして、プロセスは独立した実行単位として扱われ、プロセスごとに独立した仮想アドレス空間を持つ。&lt;br /&gt;
一方、同じプロセス内のスレッドは、プロセスが持つメモリ空間などを共有する。&lt;br /&gt;
この違いが、後述するプロセス間通信を理解するうえで重要になる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;なぜプロセス間通信が必要なのか？&lt;/h1&gt;
&lt;p&gt;プロセスごとにメモリ空間が分かれているのであれば、別のプロセスが持っているデータをどうやって利用するのだろうか。&lt;br /&gt;
例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プロセスA                 プロセスB

┌──────────────┐        ┌──────────────┐
│ メモリ空間A   │        │ メモリ空間B   │
│              │        │              │
│ &quot;Hello&quot;      │        │              │
└──────────────┘        └──────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロセスAが持っている &lt;code&gt;&quot;Hello&quot;&lt;/code&gt; というデータを、プロセスBに渡したいとする。&lt;br /&gt;
プロセスAのメモリを、プロセスBが通常の変数のように直接読み取れるわけではない。&lt;br /&gt;
そこで、プロセス同士がデータをやり取りするための仕組みが必要になる。&lt;/p&gt;
&lt;p&gt;それが&lt;strong&gt;プロセス間通信（IPC）&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;プロセス間通信（IPC）とは？&lt;/h1&gt;
&lt;p&gt;IPCは「Inter-Process Communication」の略で、日本語では「プロセス間通信」と呼ばれる。&lt;br /&gt;
ここで最初に誤解していたのが、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IPCという名前の通信プロトコルが存在する&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;というイメージだった。&lt;br /&gt;
実際には、IPCは特定の1つのプロトコルを指す言葉ではない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;複数のプロセスがデータをやり取りするための仕組みを総称してIPCと呼ぶ。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windowsの公式ドキュメントでも、IPCの方式としてパイプ、共有メモリ、RPCなど複数の方式が紹介されている。&lt;br /&gt;
代表的なものには、次のような方式がある。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;概要&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;パイプ&lt;/td&gt;
&lt;td&gt;プロセス間でデータをストリームとして受け渡す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Named Pipe&lt;/td&gt;
&lt;td&gt;名前を付けたパイプを使ってプロセス間で通信する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unix Domain Socket&lt;/td&gt;
&lt;td&gt;Unix系OSで同一マシン上のプロセス間通信などに利用される&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;共有メモリ&lt;/td&gt;
&lt;td&gt;複数プロセスからアクセス可能なメモリ領域を利用する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メッセージキュー&lt;/td&gt;
&lt;td&gt;メッセージをキューに入れてプロセス間で受け渡す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPC&lt;/td&gt;
&lt;td&gt;別プロセスの処理を呼び出すような形で通信する&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IPC
├─ パイプ
├─ Named Pipe
├─ Unix Domain Socket
├─ 共有メモリ
├─ メッセージキュー
└─ RPC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という関係になる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;パイプとは？&lt;/h1&gt;
&lt;p&gt;パイプは、プロセス間でデータを受け渡すための仕組み。&lt;br /&gt;
基本的には、一方のプロセスがデータを書き込み、もう一方のプロセスがデータを読み取る。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プロセスA
   │
   │ 書き込み
   ▼
┌──────────┐
│  パイプ   │
└──────────┘
   │
   │ 読み取り
   ▼
プロセスB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windowsでは、匿名パイプとNamed Pipeが用意されている。&lt;br /&gt;
匿名パイプは、主に親プロセスと子プロセスなど、関連するプロセス間のデータ受け渡しに利用される。&lt;/p&gt;
&lt;p&gt;一方、Named Pipeは名前を持つため、関連のないプロセス同士でも通信できる。Windowsでは同一コンピューター上だけでなく、ネットワーク越しの通信にも利用できる。&lt;br /&gt;
今回はWindows環境でのIPCを実際に試すため、Named Pipeを使用する。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;ソケットとは？&lt;/h1&gt;
&lt;p&gt;ソケットは、プロセス同士がデータを送受信するための通信エンドポイントとして利用される。&lt;br /&gt;
ここで、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ソケット通信とプロセス間通信は何が違うの？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という疑問が出てきた。&lt;br /&gt;
調べてみると、ソケット自体はIPCだけに使われるものではない。&lt;/p&gt;
&lt;p&gt;例えばTCPソケットでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client
   │
   │ TCP/IP
   ▼
Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のようにネットワークを介した通信ができる。&lt;/p&gt;
&lt;p&gt;一方、Unix Domain Socketのように、同一OS上のプロセス間通信に利用されるソケットもある。&lt;br /&gt;
Node.jsの &lt;code&gt;node:net&lt;/code&gt; でも、TCP用のソケットとIPC用のエンドポイントを扱うことができ、WindowsではIPCエンドポイントとしてNamed Pipeが使用される。
そのため、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ソケット ＝ IPC&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ではない。&lt;br /&gt;
また、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IPC ＝ 特定の通信方式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;でもない。&lt;br /&gt;
このあたりは、今回調べていて特に混乱しやすかった部分だった。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;共有メモリとは？&lt;/h1&gt;
&lt;p&gt;共有メモリは、複数のプロセスからアクセスできるメモリ領域を利用するIPCの方式。&lt;br /&gt;
通常、プロセスごとに独立した仮想アドレス空間を持つ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プロセスA                 プロセスB

┌──────────────┐        ┌──────────────┐
│ メモリ空間A   │        │ メモリ空間B   │
└──────────────┘        └──────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;共有メモリでは、OSなどの仕組みによって複数のプロセスからアクセスできる共有領域を用意する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;プロセスA                 プロセスB
    │                         │
    │                         │
    └─────────┐   ┌───────────┘
              ▼   ▼
        ┌──────────────┐
        │   共有メモリ   │
        └──────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのため、プロセス間で大量のデータをやり取りする場合などに利用される。&lt;br /&gt;
Windowsの現行ドキュメントでも、IPC方式の選択肢として共有メモリが挙げられており、大量データや高性能が必要なケースに適した方式として整理されている。&lt;/p&gt;
&lt;p&gt;ただし、複数プロセスが同じ領域へアクセスするため、データの整合性を保つための同期処理なども考える必要がある。&lt;br /&gt;
今回はNode.jsでの共有メモリの詳細な検証は行わず、まずIPCの代表的な方式として理解するところまでにする。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;REST APIでよくない？&lt;/h1&gt;
&lt;p&gt;ここが今回、一番気になったところだった。&lt;br /&gt;
自分は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTPを使ったREST APIの方が分かりやすいし、わざわざIPCを使わなくてもいいのでは？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と思っていた。&lt;br /&gt;
例えばREST APIなら、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Node.js Client
      │
      │ HTTP Request
      ▼
Node.js Server
      │
      │ HTTP Response
      ▼
Node.js Client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という構成を簡単にイメージできる。&lt;br /&gt;
一方、IPCにはパイプや共有メモリなど複数の方式があり、それぞれ仕組みが異なる。&lt;br /&gt;
そこで、実際にHTTPとWindows Named PipeをNode.jsで作って比較してみることにした。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;実際にHTTP通信を試してみる&lt;/h1&gt;
&lt;h2&gt;検証環境&lt;/h2&gt;
&lt;p&gt;今回の検証では、以下の環境を使用します。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;環境&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Windows 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;実行環境&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通信方式&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;プロセス&lt;/td&gt;
&lt;td&gt;Client / Serverで別プロセス&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;今回はWSLやDockerは使用しない。(準備がちょっと大変なので、、、)&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証内容&lt;/h2&gt;
&lt;p&gt;Node.jsでHTTP Serverを作成し、別プロセスからClientを起動してリクエストを送信する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Node.js Client
      │
      │ HTTP
      │ TCP/IP
      ▼
Node.js Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clientから、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /ping
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を送信し、Serverからレスポンスを返す。&lt;/p&gt;
&lt;h3&gt;実装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;onst server = http.createServer((req, res) =&amp;gt; {
  console.log(`[HTTP Server] PID=${process.pid}`);
  console.log(`[HTTP Server] ${req.method} ${req.url}`);

  if (req.method === &apos;GET&apos; &amp;amp;&amp;amp; req.url === &apos;/ping&apos;) {
    res.writeHead(200, {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
    });

    res.end(JSON.stringify({
      message: &apos;pong&apos;,
    }));

    return;
  }

  res.writeHead(404);
  res.end();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ryo50/ipc-test/blob/main/http/server.js&quot;&gt;http/server.js (GitHub)&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;確認すること&lt;/h2&gt;
&lt;h3&gt;ClientとServerが別プロセスであること&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ServerのPID&lt;/li&gt;
&lt;li&gt;ClientのPID&lt;/li&gt;
&lt;li&gt;PIDが異なること&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ node client.js
=== HTTP Client ===
PID: 23304

$ node server.js
=== HTTP Server ===
PID: 28700
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTTP通信の接続先&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;IPアドレス&lt;/li&gt;
&lt;li&gt;ポート番号&lt;/li&gt;
&lt;li&gt;HTTPメソッド&lt;/li&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ node client.js
=== HTTP Client ===
Target: http://127.0.0.1:3000/ping
Status: 200
Response: {&quot;message&quot;:&quot;pong&quot;}

$ node server.js
=== HTTP Server ===
Address: http://127.0.0.1:3000
[HTTP Server] PID=28700
[HTTP Server] GET /ping
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TCPを使用していること&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ServerがTCPポートで待ち受けていること&lt;/li&gt;
&lt;li&gt;Clientがそのポートへ接続していること&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\XXXXX&amp;gt; Get-NetTCPConnection -LocalPort 3000

LocalAddress                        LocalPort RemoteAddress                       RemotePort State       AppliedSetting
------------                        --------- -------------                       ---------- -----       --------------
127.0.0.1                           3000      0.0.0.0                             0          Listen
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;【Server PID】: OK&lt;/li&gt;
&lt;li&gt;【Client PID】: OK&lt;/li&gt;
&lt;li&gt;【接続先】: OK&lt;/li&gt;
&lt;li&gt;【HTTPレスポンス】:OK&lt;/li&gt;
&lt;li&gt;【TCPポートの確認結果】:OK&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Windows Named Pipeを試してみる&lt;/h1&gt;
&lt;p&gt;今回はUnix Domain Socketではなく、WindowsのNamed Pipeを使用する。&lt;br /&gt;
Node.jsの &lt;code&gt;node:net&lt;/code&gt; は、WindowsではIPCエンドポイントとしてNamed Pipeを使用できる。Node.jsでは &lt;code&gt;server.listen()&lt;/code&gt; や &lt;code&gt;net.createConnection()&lt;/code&gt; にIPCのパスを指定できる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;検証内容&lt;/h2&gt;
&lt;p&gt;HTTPの検証とできるだけ同じ処理を、Named Pipeで実装する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Node.js Client
      │
      │ Windows Named Pipe
      │ \\.\pipe\example
      ▼
Node.js Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clientからメッセージを送信し、Serverがレスポンスを返す。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実装&lt;/h2&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const server = net.createServer((socket) =&amp;gt; {
  console.log(`[Named Pipe Server] PID=${process.pid}`);
  console.log(&apos;[Named Pipe Server] Client connected&apos;);

  socket.on(&apos;data&apos;, (data) =&amp;gt; {
    const message = data.toString();

    console.log(`[Named Pipe Server] Received: ${message}`);

    socket.write(&apos;OK&apos;);
  });

  socket.on(&apos;end&apos;, () =&amp;gt; {
    console.log(&apos;[Named Pipe Server] Client disconnected&apos;);
  });

  socket.on(&apos;error&apos;, (error) =&amp;gt; {
    console.error(
      &apos;[Named Pipe Server] Socket error:&apos;,
      error.message
    );
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ryo50/ipc-test/blob/2bc50ab001401aac8c3bc2909f008c9943a563ec/named-pipe/server.js&quot;&gt;named-pipe/server.js (GitHub)&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Client&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const client = net.createConnection({
  path: PIPE_NAME,
});

client.on(&apos;connect&apos;, () =&amp;gt; {
  console.log(&apos;[Named Pipe Client] Connected&apos;);

  client.write(&apos;Hello IPC&apos;);
});

client.on(&apos;data&apos;, (data) =&amp;gt; {
  console.log(
    `[Named Pipe Client] Response: ${data.toString()}`
  );

  client.end();
});

client.on(&apos;end&apos;, () =&amp;gt; {
  console.log(&apos;[Named Pipe Client] Disconnected&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/ryo50/ipc-test/blob/main/named-pipe/client.js&quot;&gt;named-pipe/server.js (GitHub)&lt;/a&gt;&lt;/h2&gt;
&lt;h2&gt;確認すること&lt;/h2&gt;
&lt;h3&gt;ClientとServerが別プロセスであること&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Server PID&lt;/li&gt;
&lt;li&gt;Client PID&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ node server.js
=== Windows Named Pipe Server ===
PID: 5264


$ node client.js
=== Windows Named Pipe Client ===
PID: 5612
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通信先&lt;/h3&gt;
&lt;p&gt;HTTPでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のようにIPアドレスとポート番号を指定した。&lt;br /&gt;
Named Pipeでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\\.\pipe\example
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のようなPipe名を指定する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node server.js
=== Windows Named Pipe Server ===
PID: 5264
Pipe: \\.\pipe\ipc-test
[Named Pipe Server] PID=5264
[Named Pipe Server] Client connected
[Named Pipe Server] Received: Hello IPC
[Named Pipe Server] Client disconnected


$ node client.js
=== Windows Named Pipe Client ===
PID: 5612
Pipe: \\.\pipe\ipc-test
[Named Pipe Client] Connected
[Named Pipe Client] Response: OK
[Named Pipe Client] Disconnected
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TCPポートを使用していないこと&lt;/h3&gt;
&lt;p&gt;HTTPと比較して、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCPポートを使用するか&lt;/li&gt;
&lt;li&gt;IPアドレスを使用するか&lt;/li&gt;
&lt;li&gt;Named Pipeをどのように指定するか&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\mainaccount&amp;gt; Get-NetTCPConnection -LocalPort 3000
Get-NetTCPConnection : プロパティ &apos;LocalPort&apos; が &apos;3000&apos; の MSFT_NetTCPConnection オブジェクトが見つかりません。プロパテ
ィの値を確認してから再試行してください。
発生場所 行:1 文字:1
+ Get-NetTCPConnection -LocalPort 3000
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (3000:UInt16) [Get-NetTCPConnection], CimJobException
    + FullyQualifiedErrorId : CmdletizationQuery_NotFound_LocalPort,Get-NetTCPConnection


PS C:\Users\mainaccount&amp;gt; Get-ChildItem \\.\pipe\ | Where-Object {
&amp;gt;&amp;gt;     $_.Name -like &quot;*ipc-test*&quot;
&amp;gt;&amp;gt; }
    ディレクトリ: \\.\pipe
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
------        1601/01/01      9:00              4 ipc-test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;【Server PID】:OK&lt;/li&gt;
&lt;li&gt;【Client PID】:OK&lt;/li&gt;
&lt;li&gt;【Named Pipeの名前】:OK&lt;/li&gt;
&lt;li&gt;【通信結果】:OK&lt;/li&gt;
&lt;li&gt;【TCPポート使用有無】:OK&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;HTTPとNamed Pipeを比較してみる&lt;/h1&gt;
&lt;p&gt;ここまでの検証結果を整理する。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;観点&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;th&gt;Windows Named Pipe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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;通信方式&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;Named Pipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPアドレス&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1&lt;/code&gt; を使用&lt;/td&gt;
&lt;td&gt;使用しない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCPポート&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3000&lt;/code&gt; を使用&lt;/td&gt;
&lt;td&gt;使用しない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP/IP&lt;/td&gt;
&lt;td&gt;TCP/IPを使用&lt;/td&gt;
&lt;td&gt;TCP/IPを使用しない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接続先&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://127.0.0.1:3000/ping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\\.\pipe\ipc-test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.jsでの実装&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node:http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node:net&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通信結果&lt;/td&gt;
&lt;td&gt;HTTP GETを送信し、&lt;code&gt;{&quot;message&quot;:&quot;pong&quot;}&lt;/code&gt; を正常に受信&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Hello IPC&lt;/code&gt; を送信し、&lt;code&gt;OK&lt;/code&gt; を正常に受信&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ここで、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTPとNamed Pipeは、どちらも別プロセス間でデータをやり取りしている&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ことを確認する。&lt;br /&gt;
つまり、HTTPとIPCは必ずしも対立する概念ではない。&lt;br /&gt;
HTTPを使って別プロセスと通信すること自体も、広い意味ではプロセス間の通信に該当する。&lt;br /&gt;
今回比較しているのは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTPというアプリケーション層の通信方式を利用する場合と、OSが提供するIPC機構であるNamed Pipeを利用する場合の違い&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と考えた方が分かりやすそう。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;セキュリティ面では何が違う？&lt;/h1&gt;
&lt;p&gt;最初に聞いた、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「プロセス間通信の方がセキュリティ面で有効」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という話についても確認する。&lt;br /&gt;
ここは単純に、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IPCだから安全&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とは言えない。&lt;br /&gt;
Named PipeではWindowsのセキュリティ機構によってアクセスを制御できる。&lt;br /&gt;
Named Pipeにはセキュリティ記述子やACLを設定でき、Clientが接続する際にもアクセスチェックが行われる。&lt;br /&gt;
そのため、今回は以下を確認する。&lt;/p&gt;
&lt;h2&gt;確認すること&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Named Pipeにアクセス制御を設定できるか&lt;/li&gt;
&lt;li&gt;Windowsのユーザー権限と関係するか&lt;/li&gt;
&lt;li&gt;権限によって接続できるかどうかが変わるか&lt;/li&gt;
&lt;li&gt;認証なしでも接続できる状態があるか&lt;/li&gt;
&lt;li&gt;ローカル専用として扱う場合に注意点があるか&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;検証結果&lt;/h3&gt;
&lt;h2&gt;確認すること&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Named Pipeにアクセス制御が設定されているか&lt;/li&gt;
&lt;li&gt;Windowsのユーザー権限がNamed Pipeへの接続に影響するか&lt;/li&gt;
&lt;li&gt;同じユーザーと別ユーザーで接続結果が変わるか&lt;/li&gt;
&lt;li&gt;Node.js側で認証処理を実装していなくても接続できるか&lt;/li&gt;
&lt;li&gt;Named Pipeをローカルプロセス間通信に利用する場合、どのようなセキュリティ上の注意が必要か&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;検証結果&lt;/h2&gt;
&lt;h3&gt;① 同じユーザーからの接続&lt;/h3&gt;
&lt;p&gt;Named Pipe Serverを &lt;code&gt;mainaccount&lt;/code&gt; で起動し、同じ &lt;code&gt;mainaccount&lt;/code&gt; からClientを実行した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server User : mainaccount
Client User : mainaccount

Connection : SUCCESS
Response   : OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server側でもClientからの接続とデータ受信を確認できた。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=== Client Connected ===
Server PID : 20392
Received   : Hello Security Test
Client disconnected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この結果から、同一Windowsユーザーで起動したClientからNamed Pipeへ正常に接続できることを確認した。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;② 別ユーザーからの接続&lt;/h3&gt;
&lt;p&gt;検証用にWindowsローカルユーザー &lt;code&gt;ipc-test-user&lt;/code&gt; を作成し、&lt;code&gt;runas&lt;/code&gt; を使用して別ユーザーのPowerShellを起動した。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;whoami
→ owner\ipc-test-user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;その状態で同じNamed PipeへClientから接続した。&lt;br /&gt;
結果は以下のとおりだった。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Connection : FAILED
Error      : EPERM
Message    : connect EPERM \\.\pipe\ipc-security-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server側では、別ユーザーからの接続時にClientの接続・データ受信は確認できなかった。&lt;br /&gt;
この結果から、今回の環境では&lt;strong&gt;Named Pipeへの接続時にWindowsユーザーの権限が影響し、&lt;code&gt;mainaccount&lt;/code&gt; では接続できた一方、&lt;code&gt;ipc-test-user&lt;/code&gt; では接続が拒否された&lt;/strong&gt;ことを確認できた。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;③ Windowsユーザー権限との関係&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;mainaccount&lt;/code&gt; と &lt;code&gt;ipc-test-user&lt;/code&gt; のグループ情報を確認した。&lt;br /&gt;
&lt;code&gt;mainaccount&lt;/code&gt; には、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BUILTIN\Administrators
BUILTIN\Users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;などのグループが含まれていた。&lt;br /&gt;
一方、&lt;code&gt;ipc-test-user&lt;/code&gt; は、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BUILTIN\Users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;に所属しており、Administratorsグループには所属していなかった。&lt;br /&gt;
今回の検証では、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mainaccount
    ↓
Named Pipeへ接続
    ↓
成功

ipc-test-user
    ↓
Named Pipeへ接続
    ↓
EPERM
    ↓
失敗
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という結果になった。&lt;/p&gt;
&lt;p&gt;したがって、&lt;strong&gt;Named Pipeは単純に「Pipe名を知っていれば誰でも接続できる」という仕組みではなく、Windowsのアクセス制御の影響を受ける&lt;/strong&gt;ことを確認できた。&lt;/p&gt;
&lt;p&gt;ただし、今回の検証だけでは「Administratorsだから接続できた」とまでは断定できない。Named Pipe作成時にWindowsが設定する既定のセキュリティ記述子や、各ユーザー・グループに設定されたアクセス権など、複数の要因が関係しそう。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;④ Node.js側の認証処理&lt;/h3&gt;
&lt;p&gt;今回のClientでは、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const client = net.createConnection({
  path: &apos;\\\\.\\pipe\\ipc-security-test&apos;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;としており、ユーザー名やパスワードをNode.js側で送信する処理は実装していない。&lt;br /&gt;
それでも同一ユーザーでは接続でき、別ユーザーでは接続が拒否された。&lt;/p&gt;
&lt;p&gt;このことから、今回の通信では&lt;strong&gt;アプリケーション側で独自のユーザー認証を実装しなくても、Windows側のアクセス制御によってNamed Pipeへの接続可否が判断されている&lt;/strong&gt;ことを確認できた。&lt;/p&gt;
&lt;p&gt;ただし、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「認証処理がない = セキュリティがない」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という意味ではない。&lt;/p&gt;
&lt;p&gt;今回確認できたのは、アプリケーション独自の認証処理を実装していなくても、Windowsのユーザー・アクセス権を利用したアクセス制御が働いているということである。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;⑤ セキュリティ上の注意点&lt;/h3&gt;
&lt;p&gt;今回の検証では、同一ユーザーからは接続でき、別ユーザーからは接続できなかった。&lt;/p&gt;
&lt;p&gt;そのため、Windows上でNamed PipeをローカルIPCとして利用する場合でも、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pipe名を知っているだけで接続できるとは限らない&lt;/li&gt;
&lt;li&gt;Windowsユーザーやグループの権限が接続可否に影響する&lt;/li&gt;
&lt;li&gt;アプリケーション側の認証処理とWindows側のアクセス制御は別の仕組み&lt;/li&gt;
&lt;li&gt;「ローカル通信だから安全」と決めつけず、Named Pipeのアクセス権を確認する必要がある&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という点に注意する必要がある。&lt;/p&gt;
&lt;p&gt;特に、機密情報を扱うNamed Pipeや、特定のユーザー・サービスからだけ接続させたいNamed Pipeでは、Windowsのセキュリティ記述子やDACLを適切に設定する必要がある。&lt;/p&gt;
&lt;h2&gt;今回の検証結果まとめ&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;確認項目&lt;/th&gt;
&lt;th&gt;結果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Named Pipeへの接続&lt;/td&gt;
&lt;td&gt;成功&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;同一ユーザーからの接続&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;成功&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;別ユーザーからの接続&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;失敗（EPERM）&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windowsユーザー権限との関係&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;あり&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js側のユーザー認証&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;実装していない&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows側のアクセス制御&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;接続結果に影響したことを確認&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP/IP&lt;/td&gt;
&lt;td&gt;使用していない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;セキュリティ上の注意&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Named Pipeのアクセス権を確認・適切に設定する必要がある&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;なお、Named Pipeはネットワーク越しにも利用できるため、「Named Pipeだから必ずローカル通信」というわけではない。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;速度面では本当に速いのか？&lt;/h1&gt;
&lt;p&gt;もう一つ気になっていたのが、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「プロセス間通信の方が速度面で速い」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という話だった。&lt;br /&gt;
これについても、実際にHTTPとNamed Pipeで簡単な性能比較を行う。&lt;/p&gt;
&lt;h2&gt;検証条件&lt;/h2&gt;
&lt;p&gt;できるだけ条件を揃えて、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client
  │
  │ &quot;Hello IPC&quot;
  ▼
Server
  │
  │ &quot;OK&quot;
  ▼
Client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;という処理を繰り返す。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10,000回&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の回数で測定する。&lt;/p&gt;
&lt;h2&gt;記録する値&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;th&gt;Named Pipe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;リクエスト数&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;送信データ&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /ping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Hello IPC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合計時間&lt;/td&gt;
&lt;td&gt;189,208.053 ms&lt;/td&gt;
&lt;td&gt;4,964.665 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;平均時間&lt;/td&gt;
&lt;td&gt;18.921 ms&lt;/td&gt;
&lt;td&gt;0.496 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;成功件数&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;失敗件数&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;測定結果&lt;/h3&gt;
&lt;p&gt;今回の測定では、HTTPの合計時間が &lt;strong&gt;189,208.053 ms&lt;/strong&gt;、Named Pipeが &lt;strong&gt;4,964.665 ms&lt;/strong&gt; となった。&lt;/p&gt;
&lt;p&gt;平均時間では、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP：&lt;strong&gt;18.921 ms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Named Pipe：&lt;strong&gt;0.496 ms&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;となり、今回の測定条件ではNamed Pipeの方が短い時間で処理された。&lt;/p&gt;
&lt;p&gt;合計時間を比較すると、Named PipeはHTTPの約 &lt;strong&gt;2.63%&lt;/strong&gt; の時間で処理されており、HTTPと比較して約 &lt;strong&gt;97.4%短い時間&lt;/strong&gt; だった。&lt;/p&gt;
&lt;p&gt;また、両方とも10,000件すべての通信に成功しており、失敗件数は0件だった。&lt;/p&gt;
&lt;h3&gt;注意点&lt;/h3&gt;
&lt;p&gt;今回の結果は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Windows11 + CPU Intel(R) Core(TM) Ultra 7 265K + Node.js v24.14.1 + 今回の実装条件&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;で測定した結果。&lt;br /&gt;
この結果だけをもって、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「Named PipeはHTTPより必ず○倍速い」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と一般化することはしない。
OS、Node.jsのバージョン、送受信するデータサイズ、接続方法、同時実行数などによって結果が変わる可能性があるため。&lt;/p&gt;
&lt;p&gt;また、今回のベンチマークでは、&lt;strong&gt;1回の通信ごとに接続して通信した後、切断する方式&lt;/strong&gt;を採用している。そのため、通信そのものだけではなく、接続・切断にかかる処理時間も測定結果に含まれている。&lt;/p&gt;
&lt;p&gt;したがって、今回の結果は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「今回のWindows + Node.js v24.14.1環境で、同じマシン上のプロセス間通信を1回ごとの接続・切断方式で10,000回実行した場合、Named Pipeの方がHTTPより短時間で処理された」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という結果とする。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;結局、REST APIでよくない？&lt;/h1&gt;
&lt;p&gt;今回の記事を書くきっかけになった疑問に戻る。&lt;br /&gt;
最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「分かりづらいIPCを使うくらいならREST APIでよくない？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と思っていた。
しかし、調べていくと、そもそもIPCとREST APIを単純に二択で比較すること自体が適切ではないことが分かってきた。&lt;/p&gt;
&lt;p&gt;IPCは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;プロセス同士が通信するための仕組み&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;を表す広い概念。&lt;/p&gt;
&lt;p&gt;一方、REST APIは、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTPなどを利用してリソースを操作するためのAPI設計の考え方&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IPC
│
├─ Named Pipe
├─ Shared Memory
├─ Unix Domain Socket
└─ その他
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP
│
└─ REST API
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;は、そもそも同じ分類ではない。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;今回の検証から分かったこと&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;【IPCについて分かったこと】&lt;/li&gt;
&lt;li&gt;【HTTPとNamed Pipeの違い】&lt;/li&gt;
&lt;li&gt;【速度について分かったこと】&lt;/li&gt;
&lt;li&gt;【セキュリティについて分かったこと】&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;REST APIを使えばよいケース&lt;/h2&gt;
&lt;p&gt;HTTP/REST APIは、通信相手や利用環境を限定せず、ネットワーク越しにサービスを利用させたい場合に向いている。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ネットワーク越しに通信したい&lt;/li&gt;
&lt;li&gt;他のシステムやサービスから利用したい&lt;/li&gt;
&lt;li&gt;HTTPを利用できるクライアントからアクセスしたい&lt;/li&gt;
&lt;li&gt;APIとして外部に公開したい&lt;/li&gt;
&lt;li&gt;言語や実行環境が異なるシステムと連携したい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったケースでは、HTTP/REST APIが扱いやすい選択肢になる。&lt;br /&gt;
特にHTTPは、多くのクライアントや開発ツールから利用でき、APIとして設計・公開しやすいことがメリット。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;IPCを検討するケース&lt;/h2&gt;
&lt;p&gt;IPCは、同じコンピューター上で動作するプロセス同士を通信させたい場合に検討できる。&lt;br /&gt;
例えば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同じPC上のアプリケーション間で通信したい&lt;/li&gt;
&lt;li&gt;外部ネットワークからアクセスさせる必要がない&lt;/li&gt;
&lt;li&gt;HTTP/TCPを使う必要がない&lt;/li&gt;
&lt;li&gt;OSが提供するIPC機構を利用したい&lt;/li&gt;
&lt;li&gt;プロセス間で高速なデータ交換を行いたい&lt;/li&gt;
&lt;li&gt;Windows環境であればNamed Pipeを利用したい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったケース。
今回の検証では、Windows Named Pipeを使用することでTCP/IPを介さずにプロセス間通信ができ、
今回の測定条件ではHTTPより短い時間で処理できることを確認した。&lt;br /&gt;
Windowsの公式ドキュメントでも、Named Pipeはストリーム型の双方向通信、共有メモリは大量データ・高性能用途など、IPC方式ごとに適した用途が整理されている。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;今回はDockerの検証をしない理由&lt;/h1&gt;
&lt;p&gt;今回、IPCについて調べるきっかけになったのはDockerやマイクロサービスだった。&lt;/p&gt;
&lt;p&gt;ただし、Dockerまで含めると、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コンテナ&lt;/li&gt;
&lt;li&gt;IPC namespace&lt;/li&gt;
&lt;li&gt;ネットワーク&lt;/li&gt;
&lt;li&gt;コンテナ間通信&lt;/li&gt;
&lt;li&gt;Unix Domain Socket&lt;/li&gt;
&lt;li&gt;DockerのIPC設定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、別の話題が増えるのでまた後日。&lt;/p&gt;
&lt;p&gt;とりあえず&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IPCとは何なのか？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;を理解することを目的とする。&lt;br /&gt;
今回の記事で新たに出てきた、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dockerコンテナ同士ではIPCをどうやって行うのか？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;という疑問は、次の記事のテーマにでもする。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;次に調べたいこと&lt;/h1&gt;
&lt;p&gt;今回の調査から、さらに次の疑問が出てきた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dockerコンテナ間でもIPCは利用できるのか？&lt;/li&gt;
&lt;li&gt;コンテナごとにIPCは分離されるのか？&lt;/li&gt;
&lt;li&gt;DockerでNamed PipeやUnix Domain Socketは利用できるのか？&lt;/li&gt;
&lt;li&gt;IPC namespaceとは何を分離しているのか？&lt;/li&gt;
&lt;li&gt;コンテナ間で共有メモリを利用できるのか？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このあたりは、実際にDockerコンテナを作って検証してみたいと思う。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;今回は、Dockerやマイクロサービスを調べている中で気になった「プロセス間通信（IPC）」について調べた。&lt;/p&gt;
&lt;p&gt;最初は、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「REST APIでよくない？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と思っていた。&lt;/p&gt;
&lt;p&gt;しかし、調べてみるとIPCは特定の通信プロトコルを指す言葉ではなく、プロセス同士がデータをやり取りするための仕組みをまとめた言葉だと分かった。&lt;/p&gt;
&lt;p&gt;また、WindowsではNamed Pipeを利用したIPCをNode.jsから実装できる。&lt;/p&gt;
&lt;p&gt;今回の検証では、HTTPとNamed Pipeを実際に動かして、通信方法や接続先、TCP/IPの利用などの違いを確認した。&lt;/p&gt;
&lt;h3&gt;今回分かったこと&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;【IPCとは何か】&lt;/li&gt;
&lt;li&gt;【プロセスとプログラムの違い】&lt;/li&gt;
&lt;li&gt;【HTTPとIPCの関係】&lt;/li&gt;
&lt;li&gt;【Named Pipeの仕組み】&lt;/li&gt;
&lt;li&gt;【セキュリティ面の違い】&lt;/li&gt;
&lt;li&gt;【速度面の検証結果】&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最初の疑問だった「REST APIでよくない？」についても、必ずしもREST APIでなくても良いと勉強になった。
次回は今回の調査で残った疑問をもとに、DockerコンテナにおけるIPCについて実際に検証してみる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考資料&lt;/h2&gt;
&lt;p&gt;今回使用したGithubコード群
::github{repo=&quot;ryo50/ipc-test&quot;}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/net.html&quot;&gt;Node.js &lt;code&gt;net&lt;/code&gt; module documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/apps/develop/communication/interprocess-communication&quot;&gt;Microsoft Learn: Interprocess communication (IPC)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes&quot;&gt;Microsoft Learn: Named Pipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights&quot;&gt;Microsoft Learn: Named Pipe Security and Access Rights&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Web3とは何なのか？Web2との違いから開発現場での活用方法まで整理する</title><link>https://tech.storias-blog.com/blogs/web3_overview_and_development/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/web3_overview_and_development/</guid><description>Web3とは何なのかをブロックチェーンやNFT、スマートコントラクトの仕組みから整理し、実際の開発で採用判断するときのポイントや注意点を解説します。</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Web3とは何なのか？Web2との違いから開発現場での活用方法まで整理する&lt;/h1&gt;
&lt;p&gt;Web3という言葉を聞く機会がかなり増えてきました。&lt;/p&gt;
&lt;p&gt;以前は「暗号資産やNFTの話」というイメージが強かったですが、最近ではゲーム、金融、デジタルコンテンツなど、さまざまな分野でWeb3というキーワードが出てくるようになっています。&lt;/p&gt;
&lt;p&gt;ただ、エンジニア目線で見ると少し悩む部分があります。&lt;/p&gt;
&lt;p&gt;「結局、Web3を使うと何が嬉しいのか」
「既存システムをブロックチェーンに置き換える意味はあるのか」&lt;/p&gt;
&lt;p&gt;このあたりは、技術記事を読んでも少し分かりづらいところがあります。&lt;/p&gt;
&lt;p&gt;技術としては面白い一方で、実際のシステム開発では「どこに使うべきか」を判断することの方が重要になりそうです。&lt;/p&gt;
&lt;p&gt;今回はWeb3について、単なる用語整理ではなく、仕組みや技術要素を整理しながら、実際の開発で考えるポイントもまとめてみます。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Web3はブロックチェーンを使えば成立するのか問題&lt;/h2&gt;
&lt;p&gt;Web3と聞くと、まずブロックチェーンを思い浮かべる人が多いと思います。&lt;/p&gt;
&lt;p&gt;実際、ブロックチェーンはWeb3を支える重要な技術です。&lt;/p&gt;
&lt;p&gt;ただし、「ブロックチェーンを使ったサービス = Web3」というわけではありません。&lt;/p&gt;
&lt;p&gt;ここは少し混同されやすいポイントです。&lt;/p&gt;
&lt;p&gt;Web3で重要になる考え方は、サービス内のデータや価値を「特定企業だけが管理する状態」から変えていくことです。&lt;/p&gt;
&lt;p&gt;少し簡単に言うと、&lt;/p&gt;
&lt;p&gt;「サービスを提供している会社だけがデータを持つ」&lt;/p&gt;
&lt;p&gt;という形から、&lt;/p&gt;
&lt;p&gt;「ユーザー自身もデータや価値を管理できる」&lt;/p&gt;
&lt;p&gt;という方向を目指す考え方です。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Web2とWeb3の違い&lt;/h2&gt;
&lt;p&gt;まずは全体像を整理します。&lt;/p&gt;
&lt;h3&gt;Web2の一般的な構成&lt;/h3&gt;
&lt;p&gt;現在多く利用されているWebサービスは、Web2型の構成が中心です。&lt;/p&gt;
&lt;p&gt;例えばSNSの場合、以下のような構成になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
   |
   v
Webサービス
   |
   v
企業が管理するデータベース
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザーはサービスを利用しますが、以下のような情報はサービス提供企業が管理しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アカウント情報&lt;/li&gt;
&lt;li&gt;投稿データ&lt;/li&gt;
&lt;li&gt;利用履歴&lt;/li&gt;
&lt;li&gt;サービスルール&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この形は現在のWebサービスでは一般的です。&lt;/p&gt;
&lt;p&gt;メリットとしては、運営側がサービス改善しやすく、ユーザーも簡単に利用できます。&lt;/p&gt;
&lt;p&gt;一方で、サービス終了や仕様変更が発生した場合、ユーザー側ではコントロールできません。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Web3の考え方&lt;/h3&gt;
&lt;p&gt;Web3では、ユーザー自身がウォレットを持ち、デジタル資産や所有情報を管理します。&lt;/p&gt;
&lt;p&gt;イメージとしては以下のような構成です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ユーザー
   |
   v
ウォレット
   |
   v
ブロックチェーン
   |
   v
スマートコントラクト
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例えばゲームアイテムの場合、従来はゲーム会社のデータベース上に存在していました。&lt;/p&gt;
&lt;p&gt;Web3では、アイテムの所有情報をブロックチェーン上で管理し、ユーザー自身が所有できる仕組みを作れます。&lt;/p&gt;
&lt;p&gt;ただし、ここで注意したいのは「所有できる = 必ず価値がある」というわけではない点です。&lt;/p&gt;
&lt;p&gt;ユーザーにとって本当に必要な仕組みなのかを考える必要があります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Web3を構成する主な技術要素&lt;/h2&gt;
&lt;p&gt;Web3は1つの技術ではありません。&lt;/p&gt;
&lt;p&gt;複数の技術を組み合わせた考え方です。&lt;/p&gt;
&lt;p&gt;代表的なものは以下です。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ブロックチェーン&lt;/li&gt;
&lt;li&gt;ウォレット&lt;/li&gt;
&lt;li&gt;スマートコントラクト&lt;/li&gt;
&lt;li&gt;NFT&lt;/li&gt;
&lt;li&gt;DAO&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;それぞれ役割が異なります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ブロックチェーンとは何か&lt;/h2&gt;
&lt;p&gt;ブロックチェーンは、複数の参加者で同じデータを管理する仕組みです。&lt;/p&gt;
&lt;p&gt;通常のWebサービスでは、企業が管理するデータベースに情報を保存します。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;アプリ
 |
 v
データベース
&lt;/code&gt;&lt;/pre&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;ul&gt;
&lt;li&gt;データ改ざんへの耐性がある&lt;/li&gt;
&lt;li&gt;取引履歴を確認できる&lt;/li&gt;
&lt;li&gt;複数組織間で同じ情報を共有しやすい&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;デメリット&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;大量データ処理には向かない場合がある&lt;/li&gt;
&lt;li&gt;通常のDBより処理速度が遅いケースがある&lt;/li&gt;
&lt;li&gt;データ削除が難しい&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;利用するケース&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;暗号資産&lt;/li&gt;
&lt;li&gt;デジタル資産管理&lt;/li&gt;
&lt;li&gt;取引履歴管理&lt;/li&gt;
&lt;li&gt;複数企業間の情報共有&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個人的なデータ管理や社内システムでは、無理にブロックチェーンを利用する必要がないケースも多いです。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;スマートコントラクトとは何か&lt;/h2&gt;
&lt;p&gt;スマートコントラクトは、ブロックチェーン上で動作するプログラムです。&lt;/p&gt;
&lt;p&gt;簡単に言うと、「条件を満たしたら自動的に処理する仕組み」です。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支払いが完了したら権利を移転する&lt;/li&gt;
&lt;li&gt;条件を満たしたらNFTを発行する&lt;/li&gt;
&lt;li&gt;投票結果に応じて処理する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったことができます。&lt;/p&gt;
&lt;p&gt;EthereumではSolidityという言語を使って開発するケースが多いようです。&lt;/p&gt;
&lt;p&gt;例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;contract Payment {
    function transfer(address receiver) public payable {
        payable(receiver).transfer(msg.value);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記は簡略化した送金処理の例です。&lt;/p&gt;
&lt;p&gt;実際の開発では、資産管理や権利管理など、より複雑な処理を実装します。&lt;/p&gt;
&lt;h2&gt;スマートコントラクトのメリット・デメリット&lt;/h2&gt;
&lt;p&gt;スマートコントラクトは、従来は人や企業が仲介していた処理をプログラムで自動化できる点が特徴です。&lt;/p&gt;
&lt;p&gt;ただし、便利な反面、通常のWebアプリケーションとは違う難しさがあります。&lt;/p&gt;
&lt;h3&gt;メリット&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;仲介処理を減らせる&lt;/li&gt;
&lt;li&gt;ルールをコードとして公開できる&lt;/li&gt;
&lt;li&gt;条件に応じた自動処理ができる&lt;/li&gt;
&lt;li&gt;実行履歴を確認できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例えば、ゲーム内アイテムの所有権移転や、デジタルコンテンツの販売処理などで活用できます。&lt;/p&gt;
&lt;h3&gt;デメリット&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;バグ修正が難しい&lt;/li&gt;
&lt;li&gt;セキュリティ問題の影響が大きい&lt;/li&gt;
&lt;li&gt;開発難易度が高い&lt;/li&gt;
&lt;li&gt;処理内容によっては手数料が発生する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常のWebアプリケーションの場合、問題が発生したら修正版をリリースできます。&lt;/p&gt;
&lt;p&gt;しかし、スマートコントラクトでは公開後の変更が難しいケースがあります。&lt;/p&gt;
&lt;p&gt;そのため、設計段階でのレビューやテストがかなり重要になります。&lt;/p&gt;
&lt;p&gt;「とりあえず作って後から直す」という開発スタイルとは相性が悪い技術です。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;NFTは画像販売の技術なのか問題&lt;/h2&gt;
&lt;p&gt;NFTという言葉を聞くと、デジタルアート販売をイメージする人が多いと思います。&lt;/p&gt;
&lt;p&gt;実際、一時期はNFTアートが大きな話題になりました。&lt;/p&gt;
&lt;p&gt;ただ、NFTの本質は「画像を売る技術」ではありません。&lt;/p&gt;
&lt;p&gt;NFTは、デジタルデータに対して「これは誰が所有しているものなのか」を管理する仕組みです。&lt;/p&gt;
&lt;p&gt;例えば、以下のような利用方法があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ゲームアイテム&lt;/li&gt;
&lt;li&gt;デジタル会員証&lt;/li&gt;
&lt;li&gt;イベントチケット&lt;/li&gt;
&lt;li&gt;修了証や証明書&lt;/li&gt;
&lt;li&gt;コレクションアイテム&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;メリット&lt;/h3&gt;
&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;p&gt;例えばゲームの場合、ユーザーが入手したアイテムを別のユーザーへ譲渡するような仕組みを作れます。&lt;/p&gt;
&lt;p&gt;従来のゲームでは、アイテムは基本的にゲーム運営会社のデータベース上の情報でした。&lt;/p&gt;
&lt;p&gt;NFTを利用すると、ユーザー自身が所有するデジタル資産として扱う設計も可能になります。&lt;/p&gt;
&lt;h3&gt;デメリット&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;NFT自体が価値を保証するわけではない&lt;/li&gt;
&lt;li&gt;法律上の所有権とは別に考える必要がある&lt;/li&gt;
&lt;li&gt;ユーザー体験が複雑になりやすい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここはゲーム開発などでは特に注意したいポイントです。&lt;/p&gt;
&lt;p&gt;「アイテムをNFT化すればユーザーが喜ぶ」という単純な話ではありません。&lt;/p&gt;
&lt;p&gt;ユーザーにとって、&lt;/p&gt;
&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;p&gt;を考える必要があります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;DAOとは何か&lt;/h2&gt;
&lt;p&gt;DAO（Decentralized Autonomous Organization）は、中央管理者を置かずに運営する組織形態です。&lt;/p&gt;
&lt;p&gt;日本語では「分散型自律組織」と呼ばれます。&lt;/p&gt;
&lt;p&gt;従来の組織では、管理者が意思決定する形が一般的です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;管理者
 |
 v
メンバー
 |
 v
意思決定
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一方、DAOでは参加者による投票やルールによって運営する仕組みを目指します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;参加者
 |
 v
投票・ルール
 |
 v
スマートコントラクト
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;メリット&lt;/h3&gt;
&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;h3&gt;デメリット&lt;/h3&gt;
&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;p&gt;DAOは面白い考え方ですが、すべてのサービスに向いているわけではありません。&lt;/p&gt;
&lt;p&gt;例えば企業サービスの場合、迅速な意思決定が必要な場面では従来型の組織運営の方が適しているケースがあります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Web3を採用する前に考えるポイント&lt;/h2&gt;
&lt;p&gt;Web3関連の技術を見ると、「新しい技術だから使ってみたい」と感じることがあります。&lt;/p&gt;
&lt;p&gt;ただ、システム開発では技術を採用する理由が重要です。&lt;/p&gt;
&lt;p&gt;個人的には、以下のような順番で考えるのが良いと思っています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;解決したい課題
        |
        v
Web3で解決できるか
        |
        v
ブロックチェーンが必要か
        |
        v
具体的な技術選択
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;いきなり「NFTを使う」「ブロックチェーンを導入する」から考えると、目的と手段が逆になりやすいです。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;実務ではWeb3をどう使うのか&lt;/h2&gt;
&lt;p&gt;実際の開発現場では、サービス全体をWeb3化するケースはそこまで多くありません。&lt;/p&gt;
&lt;p&gt;多くの場合、一部の機能で活用できるかを検討します。&lt;/p&gt;
&lt;p&gt;例えば以下のようなケースです。&lt;/p&gt;
&lt;h3&gt;デジタル資産を扱うサービス&lt;/h3&gt;
&lt;p&gt;ゲームアイテムやデジタルコンテンツなど、ユーザーが所有すること自体に価値がある場合です。&lt;/p&gt;
&lt;p&gt;この場合、NFTやウォレットの仕組みが候補になります。&lt;/p&gt;
&lt;p&gt;ただし、ゲームの場合は注意が必要です。&lt;/p&gt;
&lt;p&gt;すべてのデータをオンチェーン化すると、&lt;/p&gt;
&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;p&gt;が問題になる可能性があります。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有権だけブロックチェーン管理&lt;/li&gt;
&lt;li&gt;ゲーム内ステータスは通常DB管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;のような構成も考えられます。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;複数企業でデータ共有するサービス&lt;/h3&gt;
&lt;p&gt;複数企業が関わる場合、1社だけがデータを管理すると信頼性の問題が発生することがあります。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&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;p&gt;などです。&lt;/p&gt;
&lt;p&gt;このような「誰か1社を完全には信用できない」という状況では、ブロックチェーンのメリットが出やすくなります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;ユーザー参加型サービス&lt;/h3&gt;
&lt;p&gt;ユーザーがサービス運営や意思決定に関わるサービスでは、DAO的な考え方が活用される場合があります。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&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;p&gt;などです。&lt;/p&gt;
&lt;p&gt;ただし、投票システムを作ればDAOになるわけではありません。&lt;/p&gt;
&lt;p&gt;参加者が継続的に関わる仕組みや、価値提供の設計が重要になります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Web3が向いていないケース&lt;/h2&gt;
&lt;p&gt;Web3は新しい技術ですが、すべてのシステムに適しているわけではありません。&lt;/p&gt;
&lt;p&gt;むしろ、従来型のWebシステムの方が良いケースも多くあります。&lt;/p&gt;
&lt;h3&gt;社内業務システム&lt;/h3&gt;
&lt;p&gt;社内だけで利用するシステムでは、基本的に管理者が明確です。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&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;p&gt;などです。&lt;/p&gt;
&lt;p&gt;このようなシステムでは、通常のデータベースやクラウドサービスを利用した方が、開発や運用はシンプルになります。&lt;/p&gt;
&lt;p&gt;「分散管理する必要があるのか」を考えることが重要です。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;高速な大量データ処理&lt;/h3&gt;
&lt;p&gt;ブロックチェーンは、データの信頼性や透明性を重視する仕組みです。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&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;p&gt;のような処理は、一般的なデータ基盤の方が向いています。&lt;/p&gt;
&lt;p&gt;例えば、アクセスログを毎秒大量に保存するようなシステムを、すべてブロックチェーンで管理するメリットはあまりありません。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;頻繁なデータ変更が必要なサービス&lt;/h3&gt;
&lt;p&gt;ブロックチェーンは「履歴を残す」ことが得意です。&lt;/p&gt;
&lt;p&gt;逆に、&lt;/p&gt;
&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;p&gt;ようなシステムでは扱いづらい場合があります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Web3開発で注意するポイント&lt;/h1&gt;
&lt;p&gt;Web3関連の開発では、通常のWebアプリケーションとは違った注意点があります。&lt;/p&gt;
&lt;p&gt;特に設計段階で考えておきたいポイントを整理します。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;秘密鍵管理&lt;/h2&gt;
&lt;p&gt;Web3サービスでは、秘密鍵の管理が非常に重要です。&lt;/p&gt;
&lt;p&gt;一般的なWebサービスでは、パスワードを忘れた場合に再設定できます。&lt;/p&gt;
&lt;p&gt;しかし、ウォレットの秘密鍵を失った場合、資産やデータへアクセスできなくなる可能性があります。&lt;/p&gt;
&lt;p&gt;これはWeb2サービスとの大きな違いです。&lt;/p&gt;
&lt;p&gt;そのため、ユーザー向けサービスでは以下のような設計が必要になります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ウォレット作成フロー&lt;/li&gt;
&lt;li&gt;秘密鍵バックアップ方法&lt;/li&gt;
&lt;li&gt;復旧方法&lt;/li&gt;
&lt;li&gt;ユーザーへの説明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技術的には安全でも、ユーザーが利用できないサービスになってしまう可能性があります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ガス代によるユーザー体験への影響&lt;/h2&gt;
&lt;p&gt;ブロックチェーンでは、処理ごとに手数料が発生する場合があります。&lt;/p&gt;
&lt;p&gt;Ethereumなどでは、この手数料をGas代と呼びます。&lt;/p&gt;
&lt;p&gt;例えばゲームで、&lt;/p&gt;
&lt;p&gt;「アイテムを取得するたびにユーザーが手数料を払う」&lt;/p&gt;
&lt;p&gt;という設計にすると、ユーザー体験が悪くなる可能性があります。&lt;/p&gt;
&lt;p&gt;そのため、以下のような工夫を行うケースがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;企業側が手数料を負担する&lt;/li&gt;
&lt;li&gt;Layer2を利用する&lt;/li&gt;
&lt;li&gt;重要な処理だけオンチェーン化する&lt;/li&gt;
&lt;li&gt;一部データをオフチェーン管理する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;すべての処理をブロックチェーン上で実行する必要はありません。&lt;/p&gt;
&lt;p&gt;どこまでブロックチェーンで管理するかが設計ポイントになります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;セキュリティ&lt;/h2&gt;
&lt;p&gt;スマートコントラクトの脆弱性は、大きな問題につながる可能性があります。&lt;/p&gt;
&lt;p&gt;通常のWebアプリケーションでは、サーバー側で問題を修正できます。&lt;/p&gt;
&lt;p&gt;しかし、スマートコントラクトでは公開後の変更が難しい場合があります。&lt;/p&gt;
&lt;p&gt;確認ポイントとしては以下があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;権限管理&lt;/li&gt;
&lt;li&gt;入力値チェック&lt;/li&gt;
&lt;li&gt;外部コントラクト呼び出し&lt;/li&gt;
&lt;li&gt;署名処理&lt;/li&gt;
&lt;li&gt;監査体制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特に資産を扱う場合は、通常のアプリケーション以上に慎重なレビューが必要になります。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;個人的には&lt;/h1&gt;
&lt;p&gt;個人的には、Web3は「既存のWebサービスを全部置き換える技術」というより、「今まで扱いづらかった所有権や信頼性をサービスに組み込むための選択肢」という位置付けが近いと思っています。&lt;/p&gt;
&lt;p&gt;例えば、一般的な会員管理システムをブロックチェーン化しても、メリットは分かりづらいです。&lt;/p&gt;
&lt;p&gt;むしろ、&lt;/p&gt;
&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;p&gt;といった問題が出る可能性があります。&lt;/p&gt;
&lt;p&gt;一方で、&lt;/p&gt;
&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;p&gt;という課題では、Web3の考え方が活きる場面があると思います。&lt;/p&gt;
&lt;p&gt;個人的には、Web3を採用するかどうかは「新しい技術だから使う」ではなく、「分散管理する価値が本当にあるか」で判断するのが良さそうです。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;今回はWeb3について整理しました。&lt;/p&gt;
&lt;p&gt;ポイントをまとめると以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web3はブロックチェーンを中心とした分散型サービスの考え方&lt;/li&gt;
&lt;li&gt;ブロックチェーン、スマートコントラクト、NFT、DAOなど複数技術で構成される&lt;/li&gt;
&lt;li&gt;重要なのはWeb3化することではなく、分散管理するメリットがあるか判断すること&lt;/li&gt;
&lt;li&gt;NFTやDAOは用途によって有効なケースと向いていないケースがある&lt;/li&gt;
&lt;li&gt;実際の開発では、UXやセキュリティを含めた設計が重要になる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Web3はまだ発展途中の技術領域です。&lt;/p&gt;
&lt;p&gt;すべてのシステムに必要なものではありませんが、所有権や信頼性が重要になるサービスでは、今後も選択肢の1つになっていくと思います。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;関連して学ぶと予定の内容&lt;/h1&gt;
&lt;p&gt;Web3をさらに理解する場合、以下のテーマを合わせて学ばないといけなさそうだったので
どこかで記事にします。。。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ブロックチェーンの仕組み&lt;/li&gt;
&lt;li&gt;Ethereumの仕組み&lt;/li&gt;
&lt;li&gt;Solidity開発&lt;/li&gt;
&lt;li&gt;スマートコントラクト設計&lt;/li&gt;
&lt;li&gt;暗号資産ウォレットの仕組み&lt;/li&gt;
&lt;li&gt;Layer2技術&lt;/li&gt;
&lt;li&gt;DApps（分散型アプリケーション）&lt;/li&gt;
&lt;li&gt;DAO設計&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WCAG（Web Content Accessibility Guidelines）とは？</title><link>https://tech.storias-blog.com/blogs/what_wcag/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/what_wcag/</guid><description>WCAGとはWebアクセシビリティのガイドライン。POUR原則（知覚・操作・理解・堅牢）の概要をまとめる。</description><pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Webサイト開発をしていると、アクセシビリティについて気にしないといけないことが増えてくる。
その中で観点として意識できていなかった**WCAG（Web Content Accessibility Guidelines）**について今回はまとめる。&lt;/p&gt;
&lt;h2&gt;WCAGとは？&lt;/h2&gt;
&lt;p&gt;WCAG（Web Content Accessibility Guidelines）は、Webコンテンツを誰でも利用しやすくするためのガイドラインである。&lt;/p&gt;
&lt;p&gt;高齢者や障害のある方だけではなく、一時的にマウスが使えない人や、スマートフォンで閲覧している人など、さまざまな利用者が快適にWebサイトを利用できることを目的としている。&lt;/p&gt;
&lt;p&gt;WCAGは以下の4つの原則（POUR）で構成されている。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;知覚可能（Perceivable）&lt;/strong&gt;
利用者が情報を認識できる状態にすること。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;画像には代替テキスト（alt）を設定する&lt;/li&gt;
&lt;li&gt;文字と背景のコントラストを十分に確保する&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;操作可能（Operable）&lt;/strong&gt;
利用者がWebサイトを操作できる状態にすること。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;キーボードのみで操作できる&lt;/li&gt;
&lt;li&gt;制限時間がある場合は延長できる&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;理解可能（Understandable）&lt;/strong&gt;
利用者が内容や操作方法を理解できること。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;わかりやすい文章で記載する&lt;/li&gt;
&lt;li&gt;入力エラーの内容や解決方法を表示する&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;堅牢（Robust）&lt;/strong&gt;
さまざまなブラウザや支援技術でも利用できること。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTMLを正しく記述する&lt;/li&gt;
&lt;li&gt;スクリーンリーダーで読み取れるマークアップを行う&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;適合レベルって？&lt;/h2&gt;
&lt;p&gt;WCAGには達成レベルが3段階存在する。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;レベル&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;最低限満たすべき要件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AA&lt;/td&gt;
&lt;td&gt;一般的に目標となるレベル&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AAA&lt;/td&gt;
&lt;td&gt;非常に高いアクセシビリティを実現するレベル&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;実際のWebサイトでは&lt;strong&gt;AAレベル&lt;/strong&gt;を目標とするケースが多いみたい。&lt;/p&gt;
&lt;h2&gt;WCAGの利点は何なのか？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;より多くの人が利用できるサイトになる&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;障害の有無に関係なく、多くの利用者が快適にWebサイトを利用できるようになる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ユーザー体験（UX）が向上する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;アクセシビリティ向上のための改善は、結果としてすべての利用者にとって使いやすいサイトにつながる。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&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;p&gt;といった改善は、アクセシビリティだけではなくUX向上にもつながる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SEOにも良い影響がある&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;適切なHTML構造や画像の代替テキストは、検索エンジンがページ内容を理解しやすくなるため、SEOにも良い影響を与えることがある。(明示はされていなさそうだった)&lt;/p&gt;
&lt;h2&gt;WCAGは対応しないとダメ？&lt;/h2&gt;
&lt;p&gt;すべてのWebサイトでWCAGへの対応が義務付けられているわけではない。
ただし、官公庁や自治体、多くの大企業では、WCAGを基準としたアクセシビリティ対応が行われているのが現状。&lt;/p&gt;
&lt;p&gt;WCAGは、W3C（World Wide Web Consortium）が策定しているWebアクセシビリティの国際的なガイドラインであり、日本でもJIS X 8341-3の基となっている。
そのため、特に企業サイトや公共機関のWebサイトでは、WCAG AAを目標として設計・実装されることが多い。&lt;/p&gt;
&lt;p&gt;また、海外ではアクセシビリティに関する法整備が進んでおり、
グローバルにサービスを展開する企業ではWCAGへの対応が求められるケースが多い。
個人開発や小規模なWebサイトでは必須となることは少ないが、アクセシビリティを考慮したサイトは利用者にとって使いやすく、結果としてユーザー体験（UX）の向上にもつながる。
そのため、必須ではなくても、Web開発を行うのであれば認識しておかないといけない。&lt;/p&gt;
&lt;p&gt;ちなみに、もしグローバルなサービスの場合は
アメリカ：ADA
EU：European Accessibility Act
等があるので把握しておく必要あり。&lt;/p&gt;
&lt;h2&gt;WCAGの注意点&lt;/h2&gt;
&lt;p&gt;WCAGは、&lt;strong&gt;チェック項目を満たせば終わりというものではない。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例えば色のコントラストを満たしていても、実際には読みにくかったり、操作しづらかったりする場合がある(実体験)。
そのため、ガイドラインを満たすことだけではなく、&lt;strong&gt;実際の利用者が使いやすいかどうか&lt;/strong&gt;を意識して設計・実装することが重要。&lt;/p&gt;
&lt;p&gt;また、WCAGは定期的に改訂されており、2026年7月現在はWCAG 2.2が公開されている。
案件によって対象バージョンや適合レベルが異なるため、WCAG順守の様な依頼があるなら事前に確認しておいた方がよさげ&lt;/p&gt;
&lt;h2&gt;当ブログ（Storia&apos;s Blog）で意識したアクセシビリティ実践例&lt;/h2&gt;
&lt;p&gt;そもそも、Astroテンプレートを使っているので意識している箇所はないのだが、lighthouseでチェックしたところいくつか指摘されたので、その辺りを対応した。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ダークモード時のコントラスト比&lt;/strong&gt;
ダークモード適用時、テキストカラーと背景色のコントラスト比が足りず可読性が落ちる箇所があったため、CSS変数（HSL指定）を微調整しました。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;キーボードによる回遊（Focus Ring）&lt;/strong&gt;
Tabキーでヘッダーナビゲーションや記事カードを移動した際、現在どこにフォーカスがあるかが一目で分かるよう &lt;code&gt;focus-visible&lt;/code&gt; スタイルを明示的に付与しています。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ライトハウス（Lighthouse）でのチェック&lt;/strong&gt;
定期的にChrome DevToolsのLighthouseを実行し、Accessibilityスコアが90点以上をキープできるようマークアップ（aria-labelの付与など）を意識しています。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;また、ノンデザイナーズ・デザインブックは個人的に、色に関しての意識が変わったのでお勧め。&lt;br /&gt;
&lt;a href=&quot;https://book.mynavi.jp/nddb/&quot;&gt;ノンデザイナーズ・デザインブック&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;WCAGは、誰もが利用しやすいWebサイトを作るためのガイドライン。
基本となる考え方は以下の4つ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知覚可能（Perceivable）&lt;/li&gt;
&lt;li&gt;操作可能（Operable）&lt;/li&gt;
&lt;li&gt;理解可能（Understandable）&lt;/li&gt;
&lt;li&gt;堅牢（Robust）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;アクセシビリティというと障害者向けの対応というイメージを持ちやすいが、実際にはすべての利用者にとって使いやすいWebサイトを作るための考え方でもある。
個人的にはAAレベルを目標としておけば、最低限イケてるサイトが作れそう。&lt;/p&gt;
</content:encoded></item><item><title>メダリオンアーキテクチャについて</title><link>https://tech.storias-blog.com/blogs/medallion_architecture/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/medallion_architecture/</guid><description>Databricksなどのデータエンジニアリングでよくみるメダリオンアーキテクチャ（Bronze/Silver/Gold）を整理。</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Databricksやデータ分析基盤でよく耳にする「メダリオンアーキテクチャ」。
なぜ分けるのかが曖昧だったので勉強も兼ねて整理してみる。&lt;/p&gt;
&lt;h2&gt;メダリオンアーキテクチャとは？&lt;/h2&gt;
&lt;p&gt;メダリオンアーキテクチャ（Medallion Architecture）は、データの品質を段階的に向上させながら管理するための設計パターン。
生データをいきなり分析に利用するのではなく、&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Raw Data → 整形済みデータ → 業務利用データ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;という流れでデータを加工していく。
一般的には以下の3層で構成される。&lt;/p&gt;
&lt;h3&gt;1. Bronze（ブロンズ）&lt;/h3&gt;
&lt;p&gt;データをそのまま保存する層。
CSVやAPI、ログなど取得したデータを可能な限り&lt;strong&gt;加工せず保存&lt;/strong&gt;する。
目的は「&lt;strong&gt;元データを残すこと&lt;/strong&gt;」。
万が一加工処理に問題があっても、このデータから再作成できる。&lt;/p&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API取得データ&lt;/li&gt;
&lt;li&gt;CSVファイル&lt;/li&gt;
&lt;li&gt;ログデータ&lt;/li&gt;
&lt;li&gt;CDCデータ&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Silver（シルバー）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;分析で利用できる品質&lt;/strong&gt;までデータを整える層。
ここではデータクレンジングや形式統一などを行う。&lt;/p&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NULL値の補正&lt;/li&gt;
&lt;li&gt;重複データの削除&lt;/li&gt;
&lt;li&gt;データ型の統一&lt;/li&gt;
&lt;li&gt;テーブル同士の結合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多くの分析や機械学習では、このSilver層を利用することが多い。
「クレンジング」と一言で言っているが、マハラノビス距離/Zスコア/IQR法/LOF/k-NNなどなど
方法が様々なようなので、別記事でまとめる。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Gold（ゴールド）&lt;/h3&gt;
&lt;p&gt;業務で直接利用するデータを作る層。
BIツールやダッシュボード、レポートで参照されるデータを配置する。&lt;/p&gt;
&lt;p&gt;例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;月別売上&lt;/li&gt;
&lt;li&gt;KPI&lt;/li&gt;
&lt;li&gt;顧客別売上&lt;/li&gt;
&lt;li&gt;商品ランキング&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;利用者がSQLを書くことなく利用できる状態まで加工されていることが多い。&lt;/p&gt;
&lt;h2&gt;なぜ3層に分けるの？&lt;/h2&gt;
&lt;p&gt;一番の目的は&lt;strong&gt;データ品質を段階的に向上させること&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例えばSilverで誤った変換をしてしまった場合でも、Bronzeには元データが残っているため再作成できる。
また、Silverを共通データとして利用することで、複数の分析やAIモデルで同じデータを再利用できる。
結果として&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データ品質の向上&lt;/li&gt;
&lt;li&gt;保守性の向上&lt;/li&gt;
&lt;li&gt;デバッグのしやすさ&lt;/li&gt;
&lt;li&gt;データの再利用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったメリットがある。&lt;/p&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;p&gt;メダリオンアーキテクチャは設計パターンであり、&lt;strong&gt;物理構成を決めるものではない&lt;/strong&gt;。
ただし、メダリオンを意識せずに漠然とデータを分けてしまうと&lt;/p&gt;
&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;p&gt;といった問題も発生する。
そのため、各レイヤーや用途ごとの責務を明確にすることが重要になる。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;メダリオンアーキテクチャは、データ品質を段階的に向上させるための設計パターン。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bronze：生データを保持する&lt;/li&gt;
&lt;li&gt;Silver：分析しやすい形へ整形する&lt;/li&gt;
&lt;li&gt;Gold：業務利用向けに加工する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という役割を持つ。
「3層構成」そのものではなく、「データ品質を段階的に高める考え方」として理解するとイメージしやすいかも。
ただ、どうしてもSilverとGoldの境界があいまいな気がしてならない。。。
(GoldからGold作ったりとかはないのかなぁとか思ったり)&lt;/p&gt;
&lt;p&gt;実データを見てみないとイメージが沸かない部分もあるので、
いつか公開されてる大量データからDatabricksの試用期間枠で実際に分けてみようと思う。&lt;/p&gt;
</content:encoded></item><item><title>AIML案件では実際に何をやっているのか？</title><link>https://tech.storias-blog.com/blogs/what_aiml/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/what_aiml/</guid><description>AIML案件の具体的な業務内容を整理。AIモデル開発以外にもデータ基盤構築・権限管理・データ連携など多くの業務がある。</description><pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近案件を探していると、「AIML案件」という単語が目に付くようになってきた。
「AIML案件」と聞いても、具体的な作業内容が全然思いつかなかったので
実際のAIML案件でどのような業務を行っているのかを調べて自分なりにまとめてみた。&lt;/p&gt;
&lt;h2&gt;AIML案件とは？&lt;/h2&gt;
&lt;p&gt;AIML案件とは、AI（Artificial Intelligence）や機械学習（Machine Learning）を活用したシステムやデータ基盤を構築・運用するプロジェクトのこと。&lt;/p&gt;
&lt;p&gt;AIで使用するデータを収集したり加工したりする。
ただし、AIモデルを開発するメンバーだけで案件が成り立つわけではなさげ&lt;/p&gt;
&lt;p&gt;実際には、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学習データの生成・活用&lt;/li&gt;
&lt;li&gt;データ基盤&lt;/li&gt;
&lt;li&gt;データ連携&lt;/li&gt;
&lt;li&gt;権限管理&lt;/li&gt;
&lt;li&gt;モデル実行環境&lt;/li&gt;
&lt;li&gt;AIアプリケーション&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、多くのシステムが組み合わさって構成されている。
そのため、一般的なシステム開発と同じように、設計・実装・テスト・運用のような業務が存在する。&lt;/p&gt;
&lt;p&gt;自分なりの理解だが、主に以下に分けられる気がする。&lt;/p&gt;
&lt;h3&gt;1. データ基盤の構築&lt;/h3&gt;
&lt;p&gt;AIが利用するデータを管理するための基盤を構築する。&lt;/p&gt;
&lt;p&gt;例えば、&lt;/p&gt;
&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;p&gt;といった作業がある。
AIの精度以前に、データを正しく管理できることが重要になる。&lt;/p&gt;
&lt;h3&gt;2. 権限管理&lt;/h3&gt;
&lt;p&gt;AIML案件では、多くの部署が同じデータ基盤を利用する。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;誰が閲覧できるか&lt;/li&gt;
&lt;li&gt;誰が更新できるか&lt;/li&gt;
&lt;li&gt;AIモデルだけが利用できるデータは何か&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などを管理する仕組みが必要になる。
権限設計はセキュリティにも直結するため、重要な業務の一つ。&lt;/p&gt;
&lt;h3&gt;3. 学習データの生成・活用&lt;/h3&gt;
&lt;p&gt;AIモデルは、学習に使用するデータの品質によって性能が大きく左右される。
そのため、AIML案件では学習データを準備する作業も重要な業務の一つとなる。&lt;/p&gt;
&lt;p&gt;具体的には、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必要なデータの収集&lt;/li&gt;
&lt;li&gt;不要なデータや重複データの除去&lt;/li&gt;
&lt;li&gt;データ形式の統一&lt;/li&gt;
&lt;li&gt;ラベル付け（アノテーション）&lt;/li&gt;
&lt;li&gt;学習用・検証用・評価用データへの分割&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などを行う。&lt;/p&gt;
&lt;p&gt;また、生成AIを活用する案件では、社内文書やマニュアル、FAQなどを整理し、AIが活用しやすい形へ加工する作業を行うこともあるそう。
AIモデルはアルゴリズムだけで性能が決まるわけではなく、学習データの品質が結果に大きく影響するようなので、学習データの作成・整備はAIMLの根幹になっている。&lt;/p&gt;
&lt;h3&gt;4. データ連携&lt;/h3&gt;
&lt;p&gt;AIMLは一つのシステムだけで完結することは少ない。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;データベース&lt;/li&gt;
&lt;li&gt;他システム&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などと連携しながらデータを受け渡す仕組みを作成する。
データの流れを設計することも重要そう。&lt;/p&gt;
&lt;h3&gt;5. 運用設計&lt;/h3&gt;
&lt;p&gt;AIが関わるとはいえ、システムは作って終わりではない。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ログ取得&lt;/li&gt;
&lt;li&gt;エラー監視&lt;/li&gt;
&lt;li&gt;障害対応&lt;/li&gt;
&lt;li&gt;権限変更&lt;/li&gt;
&lt;li&gt;データ削除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、運用を考慮した設計も必要になる。&lt;/p&gt;
&lt;h2&gt;AIML案件でよく使われる技術&lt;/h2&gt;
&lt;p&gt;比較的よく利用されてそうなものは、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Apache Spark&lt;/li&gt;
&lt;li&gt;Databricks&lt;/li&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Azure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などがある。
AIよりもクラウドやデータ基盤の知識やデータの収集を求められる場面も多い。&lt;/p&gt;
&lt;h2&gt;AIモデルを作る仕事ばかりではない&lt;/h2&gt;
&lt;p&gt;AIML案件というと、データサイエンティストがAIモデルを開発しているイメージが強い。
しかし実際には、そのモデルを動かすための環境やデータ基盤を開発するエンジニアを求められている場合もある。&lt;/p&gt;
&lt;p&gt;データが正しく管理され、必要な人だけがアクセスでき、安定してAIモデルを実行できる環境があって初めて、AIは業務で活用できる。
そのため、バックエンド開発やインフラ構築の経験もAIML案件では十分に活かすことができそう。&lt;/p&gt;
&lt;h2&gt;実務視点での所感：モデル構築よりも「データ基盤と権限設計」の難易度が高い&lt;/h2&gt;
&lt;p&gt;AIML案件を調べる中で特に感じたのは、AIモデルのアルゴリズム選定以上に「マルチテナント/部署間でのデータ権限管理」や「DWH（Databricks/Snowflakeなど）の構築・CI/CD」がプロジェクトの成否を分けるという点。&lt;/p&gt;
&lt;p&gt;従来のシステム開発では「特定DBテーブルのCRUD権限」で済んでいたものが、AIMLでは「特徴量データ」「学習済みモデルのバージョン」「検証用データセット」それぞれにきめ細やかなアクセス制御が求められる。
バックエンドエンジニアやインフラエンジニアとしての経験（AWS IAMやOpenShift等のアクセス制御、TerraformによるIaC）は、AIML領域でも極めて強い武器になると実感したし、プラスでAIMLの知識も合わせていないと要件を理解することができないと感じた。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;AIML案件では、AIモデルを開発するだけではなく、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データ基盤の構築&lt;/li&gt;
&lt;li&gt;データ連携&lt;/li&gt;
&lt;li&gt;権限管理&lt;/li&gt;
&lt;li&gt;API開発&lt;/li&gt;
&lt;li&gt;運用設計&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といった業務も多く存在する。
AIそのもの以外にも「AIを安全かつ継続的に利用できる環境を作る」という点が必要なことを考えてなかった。&lt;/p&gt;
&lt;p&gt;もちろん、LLMについての知識や、DWH、特徴量の出し方などなどデータサイエンティストの知識があると
案件上強いのは言わずもがなだが、上記以外にもインフラレイヤーや権限回りの経験などは役立ちそうだった。&lt;/p&gt;
</content:encoded></item><item><title>デシリアライズ時にはstaticが無視される話</title><link>https://tech.storias-blog.com/blogs/java_deserialize_static/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/java_deserialize_static/</guid><description>Javaのデシリアライズ時にstaticフィールドが復元されない理由と仕様について解説。</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Javaでシリアライズ/デシリアライズを扱っている時に&lt;br /&gt;
「&lt;code&gt;static&lt;/code&gt;な値が復元されていない？」となったのでまとめ。&lt;/p&gt;
&lt;h2&gt;シリアライズとデシリアライズとは？&lt;/h2&gt;
&lt;p&gt;まず前提として、シリアライズとデシリアライズは以下。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;シリアライズ&lt;br /&gt;
オブジェクトを保存や通信できる形式に変換すること。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;デシリアライズ&lt;br /&gt;
保存されたデータからオブジェクトを復元すること。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Javaでは&lt;code&gt;Serializable&lt;/code&gt;を実装することでシリアライズ可能になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.Serializable;

public class User implements Serializable {
    private String name;

    public User(String name) {
        this.name = name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;staticはシリアライズ対象外&lt;/h2&gt;
&lt;p&gt;本記事のメイン。&lt;br /&gt;
&lt;code&gt;static&lt;/code&gt;はクラスに属する値であり、&lt;br /&gt;
&lt;strong&gt;インスタンスに属する値ではない。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;そのため、シリアライズ時に保存されない。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.*;

class User implements Serializable {
    static String company = &quot;OpenAI&quot;;

    String name;

    User(String name) {
        this.name = name;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {

        User user = new User(&quot;Taro&quot;);

        // シリアライズ
        ObjectOutputStream out =
            new ObjectOutputStream(new FileOutputStream(&quot;user.dat&quot;));
        out.writeObject(user);
        out.close();

        // staticの値を変更
        User.company = &quot;Google&quot;;

        // デシリアライズ
        ObjectInputStream in =
            new ObjectInputStream(new FileInputStream(&quot;user.dat&quot;));

        User loadedUser = (User) in.readObject();
        in.close();

        System.out.println(loadedUser.name);
        System.out.println(User.company);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行結果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Taro
Google
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;シリアライズ時には&lt;code&gt;company = &quot;OpenAI&quot;&lt;/code&gt;だったが&lt;br /&gt;
デシリアライズ後は&lt;code&gt;Google&lt;/code&gt;になっている。&lt;/p&gt;
&lt;p&gt;これは&lt;code&gt;static&lt;/code&gt;が保存されていないため。&lt;/p&gt;
&lt;h2&gt;なぜstaticは保存されない？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt;はインスタンスごとの値ではなく、&lt;br /&gt;
クラス全体で共有される値。&lt;/p&gt;
&lt;p&gt;つまり、シリアライズ対象の「オブジェクトの状態」には含まれない。&lt;/p&gt;
&lt;p&gt;イメージとしては以下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;インスタンス変数  -&amp;gt; オブジェクトごとのデータ
static変数       -&amp;gt; クラス共通のデータ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Javaのシリアライズは&lt;br /&gt;
「インスタンスの状態」を保存する仕組みのため&lt;br /&gt;
&lt;code&gt;static&lt;/code&gt;は対象外になる。&lt;/p&gt;
&lt;h2&gt;transientとの違い&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt;のシリアライズと似た話として&lt;code&gt;transient&lt;/code&gt;がある。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transient&lt;/code&gt;はインスタンス変数だが&lt;br /&gt;
「シリアライズ対象から除外する」ためのもの。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User implements Serializable {

    String name;

    transient String password;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;違いとしては以下。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;種類&lt;/th&gt;
&lt;th&gt;シリアライズ対象&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;static&lt;/td&gt;
&lt;td&gt;対象外&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transient&lt;/td&gt;
&lt;td&gt;明示的に対象外&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通常フィールド&lt;/td&gt;
&lt;td&gt;対象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt;に設定値などを保持している場合、&lt;br /&gt;
デシリアライズ時に想定外の値になっている可能性がある。&lt;/p&gt;
&lt;p&gt;特に以下は注意。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;キャッシュ&lt;/li&gt;
&lt;li&gt;設定情報&lt;/li&gt;
&lt;li&gt;シングルトン管理&lt;/li&gt;
&lt;li&gt;staticカウンタ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「保存されて復元される」と思っているとハマりやすい。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Javaのシリアライズでは&lt;br /&gt;
&lt;code&gt;static&lt;/code&gt;はインスタンスに属する値ではないため保存されない。&lt;/p&gt;
&lt;p&gt;そのため、デシリアライズ時には&lt;br /&gt;
現在のクラス側の&lt;code&gt;static&lt;/code&gt;値がそのまま使用される。&lt;/p&gt;
&lt;p&gt;シリアライズ対象にしたい値は&lt;br /&gt;
&lt;code&gt;static&lt;/code&gt;ではなく通常フィールドとして保持する必要がある。&lt;/p&gt;
</content:encoded></item><item><title>シリアライズ（直列化）とは？</title><link>https://tech.storias-blog.com/blogs/java_serialize/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/java_serialize/</guid><description>Javaのシリアライズ（Serializable）の仕組みと使いどころを解説。デシリアライズとの関係もまとめる。</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;「&lt;code&gt;Serializable&lt;/code&gt;を実装してね。」&lt;br /&gt;
「シリアライズすると保存できます。」&lt;/p&gt;
&lt;p&gt;Javaを書いているとよく見るが&lt;br /&gt;
そもそもシリアライズとは何なのかを整理してみる。&lt;/p&gt;
&lt;h2&gt;シリアライズとは？&lt;/h2&gt;
&lt;p&gt;シリアライズ（Serialize）は、&lt;br /&gt;
&lt;strong&gt;オブジェクトの状態を保存・送信できる形に変換すること。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;逆に、保存されたデータから&lt;br /&gt;
オブジェクトを復元することをデシリアライズという。&lt;/p&gt;
&lt;p&gt;Javaでは以下のように&lt;code&gt;Serializable&lt;/code&gt;を実装すると&lt;br /&gt;
シリアライズ可能になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.Serializable;

public class User implements Serializable {

    private String name;

    public User(String name) {
        this.name = name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;なぜシリアライズが必要？&lt;/h2&gt;
&lt;p&gt;シリアライズとは？みたいな形で調べると、よく上記のような説明を受けるが&lt;br /&gt;
保存するとき・送信するときもバイナリになるんじゃないの？&lt;br /&gt;
と疑問だったので深掘ってみた。&lt;/p&gt;
&lt;p&gt;Javaのオブジェクトは&lt;br /&gt;
基本的にメモリ上にしか存在しない。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User user = new User(&quot;Taro&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この&lt;code&gt;user&lt;/code&gt;はプログラム終了時に消える。&lt;/p&gt;
&lt;p&gt;また、そのままでは別PCに送信もできない。&lt;/p&gt;
&lt;p&gt;なぜなら、オブジェクトは単なる「メモリ上の構造」(アドレス、ポインタ等)であり&lt;br /&gt;
そのままでは保存や通信に適した形ではないため。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ファイルに保存できる形&lt;/li&gt;
&lt;li&gt;通信できる形&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;に変換する必要がある。&lt;br /&gt;
それがシリアライズ。&lt;/p&gt;
&lt;h2&gt;オブジェクトってバイナリじゃないの？&lt;/h2&gt;
&lt;p&gt;ここが混乱しやすいポイントだった。&lt;/p&gt;
&lt;p&gt;確かにオブジェクトはメモリ上ではバイナリとして存在している。&lt;/p&gt;
&lt;p&gt;ただし、そのまま &lt;strong&gt;保存・送信&lt;/strong&gt; はできない。&lt;/p&gt;
&lt;p&gt;理由としては以下。&lt;/p&gt;
&lt;h3&gt;メモリ上のデータはそのまま使えない&lt;/h3&gt;
&lt;p&gt;例えば以下のオブジェクト。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User user = new User(&quot;Taro&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メモリ上では、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クラス情報&lt;/li&gt;
&lt;li&gt;メモリアドレス&lt;/li&gt;
&lt;li&gt;JVM内部情報&lt;/li&gt;
&lt;li&gt;参照情報&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なども含めた形で保持されている。&lt;/p&gt;
&lt;p&gt;つまり、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;このメモリの0x123456にnameがある
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のような状態。&lt;/p&gt;
&lt;p&gt;しかし、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;別PC&lt;/li&gt;
&lt;li&gt;次回起動時&lt;/li&gt;
&lt;li&gt;別JVM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ではメモリアドレスが変わる。&lt;/p&gt;
&lt;p&gt;つまり、メモリ上の状態をそのままコピーしても&lt;br /&gt;
復元できない。&lt;/p&gt;
&lt;p&gt;そのため、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User
 └ name = &quot;Taro&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のように&lt;br /&gt;
「オブジェクトの状態だけ」を取り出して&lt;br /&gt;
保存可能な形式へ変換する必要がある。&lt;/p&gt;
&lt;p&gt;これがシリアライズ。&lt;/p&gt;
&lt;h2&gt;シリアライズの用途&lt;/h2&gt;
&lt;p&gt;よくある用途は以下。&lt;/p&gt;
&lt;h3&gt;1. ファイル保存&lt;/h3&gt;
&lt;p&gt;オブジェクトの状態をファイルへ保存できる。&lt;/p&gt;
&lt;p&gt;個人的にイメージしやすかったのはゲームのセーブデータ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Player
 ├ HP = 100
 ├ Level = 10
 └ Items = [...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を保存しておけば、&lt;br /&gt;
次回起動時に復元できる。&lt;/p&gt;
&lt;h3&gt;2. 通信&lt;/h3&gt;
&lt;p&gt;オブジェクトを別コンピュータへ送信できる。&lt;/p&gt;
&lt;p&gt;例えばAPI通信やSocket通信。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client → Userオブジェクト送信 → Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のようなことを行う際、&lt;br /&gt;
通信可能なデータ形式へ変換する必要がある。&lt;/p&gt;
&lt;h3&gt;3. キャッシュ&lt;/h3&gt;
&lt;p&gt;ここは私的にはイメージがなかった。&lt;br /&gt;
Redisなどへオブジェクトを保存する際にも使われる模様。&lt;/p&gt;
&lt;p&gt;メモリ上のJavaオブジェクトは&lt;br /&gt;
そのままRedisへ置けないためシリアライズ化して保存するようになっている。&lt;/p&gt;
&lt;h2&gt;Javaではどう保存される？&lt;/h2&gt;
&lt;p&gt;Java標準のシリアライズでは&lt;br /&gt;
バイナリ形式で保存される。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ObjectOutputStream out =
    new ObjectOutputStream(
        new FileOutputStream(&quot;sample.dat&quot;));

out.writeObject(user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存された&lt;code&gt;.dat&lt;/code&gt;ファイルを開くと&lt;br /&gt;
読めないバイナリデータになっている。&lt;/p&gt;
&lt;h2&gt;JSONとの違い&lt;/h2&gt;
&lt;p&gt;ファイルで保存と言えば、JSONに変換もするような…
と思ったので調べてみた。&lt;/p&gt;
&lt;p&gt;最近はJava標準シリアライズより&lt;br /&gt;
JSON変換のほうがよく使われる模様。&lt;/p&gt;
&lt;p&gt;例えば以下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Taro&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSONも広い意味では&lt;br /&gt;
「オブジェクトを保存・通信可能な形へ変換している」ため&lt;br /&gt;
シリアライズの一種とのこと。&lt;/p&gt;
&lt;p&gt;ただし、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java標準シリアライズ&lt;br /&gt;
→ Java専用のバイナリ形式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JSON&lt;br /&gt;
→ 人間が読めるテキスト形式&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という違いがある。&lt;/p&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;p&gt;Java標準シリアライズは便利だが&lt;br /&gt;
以下の問題もある。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クラス変更に弱い&lt;/li&gt;
&lt;li&gt;Java依存&lt;/li&gt;
&lt;li&gt;セキュリティ事故の原因になりやすい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そのため、最近は&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;Protocol Buffers&lt;/li&gt;
&lt;li&gt;MessagePack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などを使うことも多い模様。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;シリアライズとは&lt;br /&gt;
&lt;strong&gt;オブジェクトを保存・通信可能な形式へ変換すること。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;オブジェクトはメモリ上ではバイナリだが、&lt;br /&gt;
そのままでは&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;メモリアドレス&lt;/li&gt;
&lt;li&gt;JVM依存情報&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などを含むため&lt;br /&gt;
保存や通信には使えない。&lt;/p&gt;
&lt;p&gt;そのため、&lt;br /&gt;
「オブジェクトの状態のみ」を取り出して&lt;br /&gt;
保存できる形式へ変換する必要がある。&lt;/p&gt;
&lt;p&gt;ゲームのセーブデータや&lt;br /&gt;
ネットワーク通信などで利用される重要な仕組み。&lt;/p&gt;
</content:encoded></item><item><title>HTMLのChunkについて</title><link>https://tech.storias-blog.com/blogs/http_chunk/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/http_chunk/</guid><description>HTTP/1.1のチャンク転送エンコーディングの仕組みと使いどころを解説。HTTP/2以降との違いにも触れる。</description><pubDate>Wed, 18 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前提&lt;/h2&gt;
&lt;p&gt;HTTP/1.1では使用されるが
HTTP/2以降のプロトコルでは廃止されているので注意&lt;/p&gt;
&lt;h2&gt;そもそもChunk転送(チャンク転送)とは何者?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chunk転送&lt;/strong&gt;は、HTTP/1.1で導入されたデータ転送方式のひとつ。
サーバーがレスポンスのコンテンツ長を事前に決定できない場合や、
ストリーミングデータのように順次データを送りたい場合に使われるもの。&lt;/p&gt;
&lt;p&gt;ChatGPTの返答がリアルタイムに返ってくる動作だったり
動画ストリーミングに利用されている。&lt;/p&gt;
&lt;h2&gt;なぜにChunk転送の概念がでてきたのか&lt;/h2&gt;
&lt;p&gt;通常、HTTPレスポンスのヘッダには&lt;code&gt;Content-Length&lt;/code&gt;が含まれる。&lt;br /&gt;
これにより、ブラウザやクライアントは&lt;strong&gt;レスポンスボディの終了位置&lt;/strong&gt;を知ることができる。&lt;/p&gt;
&lt;p&gt;しかし、以下のような場合に&lt;code&gt;Content-Length&lt;/code&gt;を指定するのが難しい。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;動的に生成されるコンテンツ(ChatGPTとか)&lt;/li&gt;
&lt;li&gt;ストリーミング形式でのレスポンス(動画系)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このようなケースで使用されるのが&lt;strong&gt;チャンク転送エンコーディング&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;Content-Length&lt;/code&gt;でも実装できないか&lt;/h2&gt;
&lt;p&gt;疑問に感じた、そもそもChunkedが出てくる前は&lt;code&gt;Content-Length&lt;/code&gt;で何とかなってたはずで
ポーリング等で再現できそうだが、何が駄目なのかを調べてみた&lt;/p&gt;
&lt;h3&gt;Chunk転送が来る前のストリーミング&lt;/h3&gt;
&lt;p&gt;ストリーミングではなくプログレッシブダウンロードを行っていた
&lt;code&gt;Content-Length&lt;/code&gt; は付与するけど、実際の再生は受信しながら再生になる
キャッシュとして端末に動画をダウンロードしながらダウンロードが終わった個所から再生していく形&lt;/p&gt;
&lt;h4&gt;プログレッシブではだめなの？&lt;/h4&gt;
&lt;p&gt;プログレッシブダウンロードはストリーミングと違い、
一時ファイルが保存される形になるので著作権面で注意する必要がある
また、動画として事前にファイルが用意できないライブ配信には不向きな点があった&lt;/p&gt;
&lt;p&gt;そのため、ストリーミング配信が最近は主流になっている&lt;/p&gt;
&lt;h3&gt;Chunk転送が来る前の動的に生成されるコンテンツ(チャットなど)&lt;/h3&gt;
&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;p&gt;を活用して処理をトリガーすることで生成されるコンテンツを読み込んでいた&lt;/p&gt;
&lt;p&gt;それぞれ簡単にまとめると
ポーリング：クライアント側で定期的に処理を実行し(10秒ごと等)新着があるかサーバーに問い合わせる&lt;/p&gt;
&lt;p&gt;ロングポーリング：リクエストされた後に処理が更新されるまでレスポンスを返さない、クライアントはレスポンス
されたらすぐにリクエストをして待機&lt;/p&gt;
&lt;p&gt;定期的リロード：ページをリロードしてコンテンツを再取得する&lt;br /&gt;
といった形で処理をトリガーしていた&lt;/p&gt;
&lt;h4&gt;ポーリング等ではだめなの？&lt;/h4&gt;
&lt;p&gt;いつ新着のコンテンツが発生するか分からないため
定期的 or 待機をする必要があり、無駄な処理が嵩む事になる
また、ロングポーリングに至っては接続が発生しっぱなしになるため、コネクションとしても効率が悪い&lt;/p&gt;
&lt;p&gt;そのため、プロトコル的にも「イベント単位のプッシュ」に最適化されているチャンク転送の方が使い勝手がよい&lt;/p&gt;
&lt;h3&gt;Chunkedが出る前(HTTP/1.0とか)のKeep-Aliveは何だったのか？&lt;/h3&gt;
&lt;p&gt;HTTP/1.0や初期のHTTP/1.1では、一応「コネクションを閉じない」設定（Keep-Alive）だけはあった
ただし、レスポンス自体は最初に&lt;code&gt;Content-Length&lt;/code&gt;が必要で、ストリーミング的な更新は無理だった&lt;/p&gt;
&lt;h2&gt;チャンク転送の仕組み&lt;/h2&gt;
&lt;p&gt;チャンク転送では、以下のようにデータを**チャンク（塊）**に分けて送信している
サーバーがレスポンスを小分けにして送るようなイメージ&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 200 OK  
Transfer-Encoding: chunked  
...
各チャンクの先頭にチャンクサイズ（16進数）を記載
チャンクサイズの後に改行（CRLF）
チャンクのデータ本体
チャンクの末尾にも改行（CRLF）
...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;チャンク転送の終わりは、サイズ&lt;code&gt;0&lt;/code&gt;のチャンクを送信することで終わりを示す&lt;/p&gt;
&lt;h2&gt;SSE&lt;/h2&gt;
&lt;p&gt;チャンク転送を使用した技術に&lt;code&gt;SSE&lt;/code&gt;という通信方法がある
サーバー → クライアント（ブラウザ）に&lt;strong&gt;イベント形式&lt;/strong&gt;のデータをストリーミングできる仕組み
&lt;code&gt;text/event-stream&lt;/code&gt; というMIMEタイプのレスポンスで実現する
JavaScriptの&lt;code&gt;EventSource&lt;/code&gt;で簡単に受信できるようになっている&lt;/p&gt;
&lt;p&gt;例えば&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const source = new EventSource(&apos;/sample&apos;);
source.onmessage = (event) =&amp;gt; console.log(event.data);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記のコードを書くことで、&lt;code&gt;/sample&lt;/code&gt;から送られてくるレスポンスを
イベントとして発火できるようになる&lt;/p&gt;
&lt;h2&gt;WebSocket&lt;/h2&gt;
&lt;p&gt;似たような仕組みに、WebSocketがある
ただ、似たような事を行っているように見えるが結構別物なので注意
こちらはWebSocketプロトコルで動作するのでHTTPではない
HTTPの仕組みと違い、&lt;strong&gt;双方向&lt;/strong&gt;で通信ができることが特徴
チャット・ゲーム・株価などの双方向、対話的な通信に特化している&lt;/p&gt;
&lt;h2&gt;HTTPリクエストスマグリング&lt;/h2&gt;
&lt;p&gt;Chunkedを知るうえで有名な攻撃手法として
&lt;code&gt;HTTPリクエストスマグリング&lt;/code&gt;と呼ばれる攻撃手法ががあったので調べてみた&lt;/p&gt;
&lt;h3&gt;そもそもHTTPリクエストスマグリングとは何か？&lt;/h3&gt;
&lt;p&gt;サーバーやプロキシ（リバースプロキシ、ロードバランサー）間で、
HTTPリクエストの解釈に差があることを突く攻撃のこと&lt;/p&gt;
&lt;p&gt;たとえば、同じリクエストをサーバーは「チャンク転送」と解釈するが、
プロキシは&lt;code&gt;Content-Length&lt;/code&gt;優先で解釈するなどの違いを利用する攻撃&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST / HTTP/1.1
Host: sample.com
Content-Length: 10
Transfer-Encoding: chunked

0

GET /sample HTTP/1.1
Host: sample.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記の場合
プロキシは&lt;code&gt;Content-Length: 10&lt;/code&gt;を信じてリクエストを終了する
しかしバックエンドサーバーはチャンク転送として扱い
その後の&lt;code&gt;GET /admin&lt;/code&gt;を別のリクエストとして解釈してしまう&lt;/p&gt;
&lt;p&gt;違いを利用することで&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;バックエンドに不正リクエストを送り込める&lt;/li&gt;
&lt;li&gt;セッション乗っ取り・キャッシュポイズニングなどを誘発する
を狙う攻撃手法の一つ&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;どうすればよいのか、、、？&lt;/h3&gt;
&lt;p&gt;対策として&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;リクエストの曖昧なヘッダ（Content-LengthとTransfer-Encodingの併用など）を許可しない&lt;/li&gt;
&lt;li&gt;Webサーバーやリバースプロキシの設定を最新化し、既知の脆弱性に対応する&lt;/li&gt;
&lt;li&gt;脆弱性スキャナ（Burp Suite など）でHTTPリクエストスマグリングを検査する
等がある&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Chunk転送(チャンク転送)はHTTP/1.1から使用できる
&lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;のヘッダーを付ける事で使用できる
動的コンテンツやストリーミングレスポンスに使うもの
HTTP/2ではチャンク転送は廃止されている(ストリームとフレームという概念が存在)&lt;/p&gt;
</content:encoded></item><item><title>オートボクシングについて</title><link>https://tech.storias-blog.com/blogs/autoboxing/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/autoboxing/</guid><description>Javaのオートボクシング（プリミティブ型⇔ラッパークラスの自動変換）の仕組みとパフォーマンスへの影響を解説。</description><pubDate>Tue, 29 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;オートボクシングとは&lt;/h2&gt;
&lt;p&gt;プリミティブ型（int、double、booleanなど）を対応するラッパークラス（Integer、Double、Booleanなど）に自動的に変換する機能のこと。
逆に、ラッパークラスをプリミティブ型に自動変換することをアンボクシング（unboxing）と呼ぶ。
オートボクシングはコードの可読性を向上させるために使ったりするが、
パフォーマンス面で注意が必要。&lt;/p&gt;
&lt;h2&gt;パフォーマンスへの影響&lt;/h2&gt;
&lt;p&gt;色々調べた感じだと以下の点でパフォーマンスへの影響がありそう。&lt;/p&gt;
&lt;h3&gt;追加のオブジェクト生成&lt;/h3&gt;
&lt;p&gt;オートボクシングではプリミティブ型をラッパークラスに変換する際、
追加のオブジェクトが生成される模様。&lt;/p&gt;
&lt;p&gt;例えば、&lt;code&gt;int&lt;/code&gt;が&lt;code&gt;Integer&lt;/code&gt;に変換されると、
新しい&lt;code&gt;Integer&lt;/code&gt;オブジェクトが作られる場合がある。&lt;a href=&quot;Java%E3%81%A7%E3%81%AF%E3%80%81%60-128%60%E3%81%8B%E3%82%89%60127%60%E3%81%AE%E7%AF%84%E5%9B%B2%E5%86%85%E3%81%AE%E6%95%B4%E6%95%B0%E5%80%A4%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AF%E3%80%81Integer%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%8C%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%95%E3%82%8C%E3%82%8B%E3%80%82%EF%BC%88%E4%BB%96%E3%81%AE%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%82%E5%90%8C%E6%A7%98%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E6%A9%9F%E6%A7%8B%E3%82%92%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%EF%BC%89&quot;&gt;^1&lt;/a&gt;
もちろんオブジェクト生成にはメモリと処理時間がかかる。
頻繁にボクシングが発生する（特に大量のデータを処理する）場合、
GC（ガーベッジコレクション）の負荷が増え、パフォーマンスに影響しそう。&lt;/p&gt;
&lt;h3&gt;アンボクシングのパフォーマンスコスト&lt;/h3&gt;
&lt;p&gt;オートボクシングが絡むと、アンボクシングも発生することになる。
例えば、&lt;code&gt;Integer&lt;/code&gt;を&lt;code&gt;int&lt;/code&gt;に変換する際には、
ラッパークラスからプリミティブ型に値を取り出す処理が必要になる。
上記処理も追加のコストを生むため、パフォーマンスに影響を与えることになる。&lt;/p&gt;
&lt;p&gt;特にループ内でボクシングしている場合や
コレクション(&lt;code&gt;List&amp;lt;Integer&amp;gt;&lt;/code&gt;や&lt;code&gt;Map&amp;lt;Integer, Integer&amp;gt;&lt;/code&gt;)のように
ラッパークラスで定義を使用している場合、
頻繁にボクシングとアンボクシングが発生する可能性が高くなるのでパフォーマンスコストがかかる。&lt;/p&gt;
&lt;p&gt;この範囲内の数値であれば、新しいオブジェクトを生成せずに既存のオブジェクトを再利用される模様。
しかし、それ以外の値では新たなIntegerオブジェクトが生成される。&lt;/p&gt;
&lt;h3&gt;実際に確認してみた&lt;/h3&gt;
&lt;p&gt;どのくらい違うのか気になったので確認してみた。
&lt;code&gt;openjdk version &quot;18.0.2.1&quot; 2022-08-18&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static void main(String[] args) {
    final int EXECUTE_COUNT = 10;
    for (int i = 0; i &amp;lt; EXECUTE_COUNT; i++) {
        System.out.println(&quot;Execute count: &quot; + i);
        execute();
    }
}

private static void execute(){
    final int LOOP_COUNT = 100_000_000;

    // オートボクシングを使わないケース
    long startTime = System.nanoTime();
    long sumPrimitive = 0;
    for (int i = 0; i &amp;lt; LOOP_COUNT; i++) {
        sumPrimitive += i;  // プリミティブ型のみ
    }
    long endTime = System.nanoTime();
    System.out.println(&quot;Without Autoboxing: &quot; + (endTime - startTime) / 1_000_000 + &quot; ms&quot;);

    // オートボクシングを使ったケース
    startTime = System.nanoTime();
    Long sumBoxed = 0L;  // ラッパークラス(Long)
    for (int i = 0; i &amp;lt; LOOP_COUNT; i++) {
        sumBoxed += i;  // オートボクシング
    }
    endTime = System.nanoTime();
    System.out.println(&quot;With Autoboxing: &quot; + (endTime - startTime) / 1_000_000 + &quot; ms&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Execute count: 0
Without Autoboxing: 41 ms
With Autoboxing: 355 ms
Execute count: 1
Without Autoboxing: 42 ms
With Autoboxing: 347 ms
Execute count: 2
Without Autoboxing: 41 ms
With Autoboxing: 350 ms
Execute count: 3
Without Autoboxing: 40 ms
With Autoboxing: 351 ms
Execute count: 4
Without Autoboxing: 40 ms
With Autoboxing: 352 ms
Execute count: 5
Without Autoboxing: 42 ms
With Autoboxing: 346 ms
Execute count: 6
Without Autoboxing: 42 ms
With Autoboxing: 348 ms
Execute count: 7
Without Autoboxing: 41 ms
With Autoboxing: 355 ms
Execute count: 8
Without Autoboxing: 40 ms
With Autoboxing: 350 ms
Execute count: 9
Without Autoboxing: 40 ms
With Autoboxing: 356 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;差はかなりありそうだが、
そもそも気にするほどパフォーマンスに問題でるような時間はかからなさそう。
ただし、ちりつもで遅くなることはありそうな気配。&lt;/p&gt;
&lt;h2&gt;パフォーマンスを良くするためには&lt;/h2&gt;
&lt;h3&gt;プリミティブ型の使用&lt;/h3&gt;
&lt;p&gt;そもそも可能であれば、不必要なラッパークラスを使わないことでオートボクシングを発生させない。
例えば、&lt;code&gt;Integer&lt;/code&gt;を使わずに&lt;code&gt;int&lt;/code&gt;を使うことで、不要なオブジェクト生成を防ぐ。&lt;/p&gt;
&lt;h3&gt;キャッシュの活用&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;Java%E3%81%A7%E3%81%AF%E3%80%81%60-128%60%E3%81%8B%E3%82%89%60127%60%E3%81%AE%E7%AF%84%E5%9B%B2%E5%86%85%E3%81%AE%E6%95%B4%E6%95%B0%E5%80%A4%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AF%E3%80%81Integer%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%8C%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%95%E3%82%8C%E3%82%8B%E3%80%82%EF%BC%88%E4%BB%96%E3%81%AE%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%82%E5%90%8C%E6%A7%98%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E6%A9%9F%E6%A7%8B%E3%82%92%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%EF%BC%89&quot;&gt;^1&lt;/a&gt;に記載したが、
扱う数値が-128から127の範囲内であればオートボクシングによるパフォーマンス低下はあまり発生しない。
もし、キャッシュ範囲外で値の範囲が定まっているオートボクシングが発生するのであれば
以下のJVMオプションをつける事でもパフォーマンス向上が期待できる。
&lt;code&gt;-XX:AutoBoxCacheMax&lt;/code&gt;
ただし、上記値を大きくすればするほど常時メモリを喰う量が大きくなるので注意が必要。&lt;/p&gt;
&lt;h2&gt;なんでラッパークラスがあるのか&lt;/h2&gt;
&lt;p&gt;そもそもプリミティブ型のみであれば、オートボクシングが発生する余地がないのでは？となったのでどうしてラッパークラスがあるのか調べてみた。&lt;/p&gt;
&lt;h3&gt;コレクションに格納するため&lt;/h3&gt;
&lt;p&gt;Javaのコレクション（List、Set、Mapなど）やジェネリクスは、オブジェクトを要素として格納することが前提となっているのでプリミティブ型を直接格納することはできない。
そのため、例えば&lt;code&gt;List&amp;lt;int&amp;gt;&lt;/code&gt;のようにプリミティブ型を直接使えない代わりに、
&lt;code&gt;List&amp;lt;Integer&amp;gt;&lt;/code&gt;とすることでプリミティブ型の値もコレクションに格納できるようになる。
プリミティブ型のままで集合を扱う場合は、配列のみになる。&lt;/p&gt;
&lt;h3&gt;プリミティブ型にはないメソッドが利用できる&lt;/h3&gt;
&lt;p&gt;ラッパークラスには、プリミティブ型の値を操作するための便利なメソッドが提供されている。
例えば、&lt;code&gt;Integer&lt;/code&gt;クラスには&lt;code&gt;parseInt&lt;/code&gt;メソッドがあり、文字列を整数に変換することができたりする。&lt;/p&gt;
&lt;h3&gt;nullの利用&lt;/h3&gt;
&lt;p&gt;プリミティブ型はnullを許容しないが、ラッパークラスはオブジェクトであるためnullを保持できる。
これにより、「値が設定されていない」や「未初期化」という状態を表現することができる。
データベースやJSONなどの外部からデータを受け取る場面で、
「値が存在しない」場合をnullで表現することがたまにあったりする。
ただし、一長一短でプリミティブで扱っていれば気にしなくて済む&lt;code&gt;null&lt;/code&gt;が入ってくるので
booleanなどは選択肢が&lt;code&gt;true&lt;/code&gt;、&lt;code&gt;false&lt;/code&gt;だけでなく&lt;code&gt;null&lt;/code&gt;も考慮に入れる必要が出てくる。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;パフォーマンスが劇的に悪くなることはないが、
&lt;code&gt;null&lt;/code&gt;の選択肢が入ってくるラッパークラスが良い場面と悪い場面がある。&lt;/p&gt;
&lt;p&gt;意識して使い分けることは必須ではないが、出来れば意識して使い分けていきたいところ。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;ボクシングのキャッシュについて凄い詳しく書かれているサイト
https://medium.com/programmingmitra-com/java-integer-cache-why-integer-valueof-127-integer-valueof-127-is-true-e5076824a3d5&lt;/p&gt;
</content:encoded></item><item><title>Javaのprotectedについて</title><link>https://tech.storias-blog.com/blogs/java_protected/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/java_protected/</guid><description>Javaのprotectedアクセス修飾子について。同一パッケージとサブクラスからのアクセスの違いを整理。</description><pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;継承を説明する時に大体出てくる&lt;code&gt;protected&lt;/code&gt;について
人に説明をしようとした時に、実はあまり理解できてなかったのでまとめ。&lt;/p&gt;
&lt;h2&gt;protectedとは？&lt;/h2&gt;
&lt;p&gt;Javaの&lt;code&gt;protected&lt;/code&gt;は、クラスのメンバー（フィールドやメソッド）のアクセス制御を行うために使用するもの。
&lt;code&gt;protected&lt;/code&gt;が付けられたメンバーは、次の場合にアクセスできる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;同じパッケージ内のクラス：
&lt;strong&gt;同じパッケージに属するクラス&lt;/strong&gt;からアクセスできる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;サブクラス（継承関係）：
&lt;strong&gt;継承しているクラス&lt;/strong&gt;からアクセスできる。
サブクラスが異なるパッケージに存在していても、継承しているクラスであればアクセスできる。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;package com.example;

public class Parent {
    protected void display() {
        System.out.println(&quot;protectedのメソッド&quot;);
    }
}

/* -------------------- */
package com.example;

public class Child extends Parent {
    public void show() {
        display();  // protectedの対象と同じパッケージ内なのでアクセス可能
    }
}

/* -------------------- */
package com.another;
import com.example.Parent;

public class AnotherChild extends Parent {
    public void show() {
        display();  // 異なるパッケージでも継承関係があるのでアクセス可能
    }
}

/* -------------------- */
package com.another;
import com.example.Parent;

public class AnotherClass {
    public void show() {
        Parent parent = new Parent();
        parent.display();  // 異なるパッケージかつ、継承関係でもないのでアクセス不可
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;protectedの利点は何？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;親クラスのデータやメソッドをprotectedにすることで、
サブクラスに対して適切な範囲でのみアクセスを許可し、外部から意図しない変更がされなくなる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;継承関係にあるクラスで、親クラスのprotectedメンバーを再利用することで、
コードの重複を避けて保守しやすいコードがかける。
サブクラスは親クラスのprotectedメンバーにアクセスできるので
親クラスの機能を拡張しやすい。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;protectedの注意点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;protected&lt;/code&gt;は別パッケージに対しては&lt;code&gt;private&lt;/code&gt;だが
同一パッケージに関しては&lt;code&gt;public&lt;/code&gt;のためクラスの内部実装が広範囲に公開される可能性がある。
継承しかさせないつもりでクラスを作ったとしても
同一パッケージのクラスからはすべて読み込めてしまうので
パッケージの作り方を気にしつつ、必要最小限の範囲で使用することがよい。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;protected&lt;/code&gt;は&lt;strong&gt;同じパッケージに属するクラス&lt;/strong&gt;か&lt;strong&gt;継承しているクラス&lt;/strong&gt;からアクセスできる。
使用箇所としては継承関係が多めな認識。
継承で再利用でき別パッケージであれば意図しないアクセスが拒否できる点がメリットそう。&lt;/p&gt;
</content:encoded></item><item><title>Javaのequalsと==について</title><link>https://tech.storias-blog.com/blogs/java_equal/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/java_equal/</guid><description>Javaの==演算子とequalsメソッドの違い。プリミティブ型と参照型での挙動の違いを深掘りしてまとめ。</description><pubDate>Wed, 03 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今更ながら、きちんと説明できるだけ深堀したことなかったのでまとめ。&lt;/p&gt;
&lt;h2&gt;そもそも&lt;code&gt;==&lt;/code&gt;演算子とは？&lt;/h2&gt;
&lt;p&gt;基本データ型（プリミティブ型）に対して使う場合、その&lt;strong&gt;値&lt;/strong&gt;を比較する。
参照型（オブジェクト型）に対して使う場合、そのオブジェクトの&lt;strong&gt;参照:メモリアドレス&lt;/strong&gt;を比較します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//プリミティブ型の比較
int a = 5;
int b = 5;
System.out.println(a == b); // true（プリミティブかつ値が同じだから）

//オブジェクト型の比較
String str1 = new String(&quot;Hello&quot;);
String str2 = new String(&quot;Hello&quot;);
System.out.println(str1 == str2); // false（異なるオブジェクトだから）
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;equals&lt;/code&gt;メソッドとは？&lt;/h2&gt;
&lt;p&gt;オブジェクトの&lt;strong&gt;内容&lt;/strong&gt;を比較するために使われる。
Objectクラスのデフォルトでは、以下のコードになっている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//Java:21
public boolean equals(Object obj) {
    return (this == obj);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特にStringクラスでは、&lt;code&gt;equals&lt;/code&gt;メソッドがオーバーライドされていて
文字列が同じかどうかを比較するように実装されている。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//Java:21
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    return (anObject instanceof String aString)
            &amp;amp;&amp;amp; (!COMPACT_STRINGS || this.coder == aString.coder)
            &amp;amp;&amp;amp; StringLatin1.equals(value, aString.value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自分でクラスを作成する場合、
&lt;code&gt;equals&lt;/code&gt;をオーバーライドして比較する&lt;strong&gt;内容&lt;/strong&gt;を決めてあげる必要がある。&lt;/p&gt;
&lt;h2&gt;なぜStringに対して&lt;code&gt;==&lt;/code&gt;を使ってはいけないのか？&lt;/h2&gt;
&lt;p&gt;よく入門書とかに書かれているString型の時にJavaでは&lt;code&gt;==&lt;/code&gt;を使ってはいけないお話。
Stringクラスに対して&lt;code&gt;==&lt;/code&gt;を使うと、オブジェクトの&lt;strong&gt;参照&lt;/strong&gt;を比較することになる。
つまり、文字列の内容が同じであっても異なるオブジェクトとして作成された場合、
&lt;code&gt;==&lt;/code&gt;は&lt;code&gt;false&lt;/code&gt;を返してしまう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String str1 = new String(&quot;Hello&quot;);
String str2 = new String(&quot;Hello&quot;);
//中身が同じなのに、、、
System.out.println(str1 == str2); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特にStringに関して、
書き方によっては文字列リテラルがプールされるため&lt;a href=&quot;%E7%B4%B0%E3%81%8B%E3%81%84%E9%83%A8%E5%88%86%E3%82%92%E6%9B%B8%E3%81%8D%E5%A7%8B%E3%82%81%E3%82%8B%E3%81%A8%E9%95%B7%E3%81%8F%E3%81%AA%E3%82%8B%E3%81%AE%E3%81%A7%E7%B0%A1%E5%8D%98%E3%81%AB%E3%80%81%E3%80%81%E3%80%81&quot;&gt;^1&lt;/a&gt;
プールされている参照が割当たった時は比較結果が上記と逆になる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//文字列リテラルをプールして参照した場合
String str1 = &quot;Hello&quot;;
String str2 = &quot;Hello&quot;;
System.out.println(str1 == str2); // true（同じリテラルを参照しているため）

//オブジェクトとしてそれぞれ定義した場合
String str3 = new String(&quot;Hello&quot;);
String str4 = new String(&quot;Hello&quot;);
System.out.println(str3 == str4); // false（異なるオブジェクトだから）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この挙動の違いが期待した結果と異なることが多いためバグの原因となる。&lt;/p&gt;
&lt;h2&gt;正しい文字列比較の方法&lt;/h2&gt;
&lt;p&gt;文字列の内容を比較する場合は、必ず&lt;code&gt;equals&lt;/code&gt;メソッドを使用すること。
正しく把握して使う分にはよいかと思うが、&lt;code&gt;==&lt;/code&gt;にするメリットが思いつかないので
Stringは&lt;code&gt;equals&lt;/code&gt;を使うこととして覚えてしまった方がよさげ。
(もちろん、理由はキチンと把握しておいた方がよいが、、、)&lt;/p&gt;
&lt;p&gt;文字列はメモリ効率化のため
特定の方法で作成するとメモリの特殊な場所に文字列を保存する。
その後、同じ文字列を作成する場合、
別の場所にメモリを確保するのではなく既にある文字列の場所を参照するようにメモリを持つイメージ&lt;/p&gt;
</content:encoded></item><item><title>JavaのfindFirstとfindAnyの違い</title><link>https://tech.storias-blog.com/blogs/java_findfirst_vs_findany/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/java_findfirst_vs_findany/</guid><description>JavaのStream APIにあるfindFirstとfindAnyの違いを解説。並列ストリームでの挙動の差がポイント。</description><pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;あまり使うことのなかった&lt;code&gt;findFirst&lt;/code&gt;と&lt;code&gt;findAny&lt;/code&gt;を調べたのでメモ&lt;/p&gt;
&lt;h3&gt;findFirstとは&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;findFirst&lt;/code&gt;メソッドは、Streamから最初の要素を返すために使用される。
&lt;strong&gt;順序付けされている状態であれば、findFirstは常に最初の要素を返す。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = Arrays.asList(&quot;Test1&quot;, &quot;Test2&quot;, &quot;Test3&quot;, &quot;Test4&quot;);
Optional&amp;lt;String&amp;gt; firstName = names.stream().findFirst();
firstName.ifPresent(System.out::println); //→Test1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;findAnyとは&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;findAny&lt;/code&gt;メソッドは、ストリームから任意の要素を返す。
特に並列でストリームを回しているときに性能が良くなるらしい。。。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;David&quot;);
Optional&amp;lt;String&amp;gt; anyName = names.parallelStream().findAny();
anyName.ifPresent(System.out::println);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;findAnyいつ使うの？&lt;/h3&gt;
&lt;p&gt;性能的に考えても&lt;code&gt;findFirst&lt;/code&gt;で最初が返されるのであれば
感覚的に&lt;code&gt;findFirst&lt;/code&gt;の方が速そう、、、&lt;/p&gt;
&lt;p&gt;なので試してみた。&lt;/p&gt;
&lt;h4&gt;前提&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;parallelStream&lt;/code&gt;自体がマルチコアプロセッサを活かして動く形なので以降の処理結果は
CPUに依存する。
XeonなどのマルチコアゴリゴリのCPU等であれば処理結果変わらない可能性がある。&lt;/p&gt;
&lt;h3&gt;parallelStreamでのfindFirstとfindAnyの比較&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        for(int i = 1; i &amp;lt; 11; i++){
            execute(i);
        }
    }
    
    
    private static void execute(int index){
        System.out.println(index + &quot;回目&quot;);

        List&amp;lt;Integer&amp;gt; largeList = IntStream.range(0, 1000000).boxed().collect(Collectors.toList());

        // findFirstの計測
        long startTime1 = System.nanoTime();
        Optional&amp;lt;Integer&amp;gt; first = largeList.parallelStream().findFirst();
        long endTime1 = System.nanoTime();
        System.out.println(&quot;findFirst took :&quot; + (endTime1 - startTime1) + &quot; ns&quot;);

        // findAnyの計測
        long startTime2 = System.nanoTime();
        Optional&amp;lt;Integer&amp;gt; any = largeList.parallelStream().findAny();
        long endTime2 = System.nanoTime();
        System.out.println(&quot;findAny took   :&quot; + (endTime2 - startTime2) + &quot; ns&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;1回目
findFirst took :533346 ns
findAny took   :76237 ns
2回目
findFirst took :349193 ns
findAny took   :70898 ns
3回目
findFirst took :163967 ns
findAny took   :100547 ns
4回目
findFirst took :140695 ns
findAny took   :106406 ns
5回目
findFirst took :146328 ns
findAny took   :74622 ns
6回目
findFirst took :293930 ns
findAny took   :95320 ns
7回目
findFirst took :141319 ns
findAny took   :51707 ns
8回目
findFirst took :147466 ns
findAny took   :63520 ns
9回目
findFirst took :138300 ns
findAny took   :283056 ns
10回目
findFirst took :163080 ns
findAny took   :91353 ns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同じように要素を少なく(10個)にしてみた結果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1回目
findFirst took :132483 ns
findAny took   :58254 ns
2回目
findFirst took :112732 ns
findAny took   :53424 ns
3回目
findFirst took :108216 ns
findAny took   :73001 ns
4回目
findFirst took :118181 ns
findAny took   :52879 ns
5回目
findFirst took :117647 ns
findAny took   :51617 ns
6回目
findFirst took :124880 ns
findAny took   :63088 ns
7回目
findFirst took :126601 ns
findAny took   :60481 ns
8回目
findFirst took :124372 ns
findAny took   :59836 ns
9回目
findFirst took :120962 ns
findAny took   :50714 ns
10回目
findFirst took :107710 ns
findAny took   :51197 ns
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;parallelStreamでのfindFirstとfindAnyの結果&lt;/h4&gt;
&lt;p&gt;結果的にはparallelStream同士では&lt;code&gt;findFirst&lt;/code&gt;の方がかなり速かった。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parallelStream&lt;/code&gt;とstreamの場合で比べてみたらどうなるのか気になったので
試してみた。&lt;/p&gt;
&lt;h3&gt;parallelStreamとStreamでのfindFirstとfindAnyの比較&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        for(int i = 1; i &amp;lt; 11; i++){
            execute(i);
        }
    }
    
    
    private static void execute(int index){
        System.out.println(index + &quot;回目&quot;);

        List&amp;lt;Integer&amp;gt; largeList = IntStream.range(0, 100000).boxed().collect(Collectors.toList());

        // findFirstの計測
        long startTime1 = System.nanoTime();
        Optional&amp;lt;Integer&amp;gt; first = largeList.stream().findFirst();
        long endTime1 = System.nanoTime();
        System.out.println(&quot;findFirst took :&quot; + (endTime1 - startTime1) + &quot; ns&quot;);

        // findAnyの計測
        long startTime2 = System.nanoTime();
        Optional&amp;lt;Integer&amp;gt; any = largeList.parallelStream().findAny();
        long endTime2 = System.nanoTime();
        System.out.println(&quot;findAny took   :&quot; + (endTime2 - startTime2) + &quot; ns&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;1回目
findFirst took :16188 ns
findAny took   :85091 ns
2回目
findFirst took :15263 ns
findAny took   :77307 ns
3回目
findFirst took :15275 ns
findAny took   :108210 ns
4回目
findFirst took :11231 ns
findAny took   :62859 ns
5回目
findFirst took :11406 ns
findAny took   :60593 ns
6回目
findFirst took :11201 ns
findAny took   :60128 ns
7回目
findFirst took :11290 ns
findAny took   :59533 ns
8回目
findFirst took :17196 ns
findAny took   :1116963 ns
9回目
findFirst took :15139 ns
findAny took   :77320 ns
10回目
findFirst took :16299 ns
findAny took   :80137 ns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらも同じように要素を少なく(10個)してみた結果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1回目
findFirst took :11960 ns
findAny took   :65212 ns
2回目
findFirst took :10200 ns
findAny took   :61745 ns
3回目
findFirst took :8921 ns
findAny took   :55540 ns
4回目
findFirst took :8800 ns
findAny took   :53469 ns
5回目
findFirst took :8788 ns
findAny took   :54545 ns
6回目
findFirst took :10299 ns
findAny took   :54831 ns
7回目
findFirst took :9144 ns
findAny took   :57409 ns
8回目
findFirst took :8523 ns
findAny took   :52583 ns
9回目
findFirst took :8659 ns
findAny took   :53152 ns
10回目
findFirst took :9056 ns
findAny took   :58739 ns
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;parallelStreamとstreamでのfindFirstとfindAnyの結果&lt;/h4&gt;
&lt;p&gt;結果的には、Streamにした状態の&lt;code&gt;findFirst&lt;/code&gt;が速かった。&lt;/p&gt;
&lt;h3&gt;まとめ&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;parallelStream&lt;/code&gt;を使用する場合は&lt;code&gt;findAny&lt;/code&gt;
&lt;code&gt;stream&lt;/code&gt;で良い場合は、&lt;code&gt;findFirst&lt;/code&gt;の方がよさげ。&lt;/p&gt;
&lt;p&gt;ただ、&lt;code&gt;parallelStream&lt;/code&gt;が必要なレベルの重たい処理を行ったことがないため、
&lt;code&gt;parallelStream&lt;/code&gt;で&lt;code&gt;findFirst&lt;/code&gt;の方がよい場合もあるのかも&lt;/p&gt;
</content:encoded></item><item><title>HTMLのscriptタグを書く位置について</title><link>https://tech.storias-blog.com/blogs/html_script_tag_placement/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/html_script_tag_placement/</guid><description>HTMLのscriptタグをbody直前・head・deferで書く場合の違いと、推奨パターンを備忘録としてまとめ。</description><pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;普段HTMLを書かないと忘れていくので備忘録もかねて&lt;/p&gt;
&lt;h2&gt;scriptタグどこに記載すればよいか問題&lt;/h2&gt;
&lt;p&gt;HTMLでscriptを記載できる位置がいくつかあり、
どこに書けばよいか分からなくなるのでそれぞれ書く位置と問題点を記載していく&lt;/p&gt;
&lt;h3&gt;scriptタグを記載できる位置&lt;/h3&gt;
&lt;p&gt;HTMLドキュメントでは、&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;タグを以下の場所に記載することができる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;タグ内：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;script src=&quot;./index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここに記載すると、HTMLの解析前にスクリプトが読み込まれて実行される。
bodyでのDOM解析より前に走るため、ページのレンダリングがブロックされることがある。
特に外部リソースが多い場合やスクリプトが重い場合、ページの表示が遅れる原因となる。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;タグ内の適切な場所：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;の最初：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;script src=&quot;./index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;!-- ページのコンテンツ --&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;の最初にスクリプトを配置すると
&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;内に配置した場合と同様に、レンダリングがブロックされる可能性がある。
&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;の時と同じく、スクリプトが重い場合はページの表示が遅れる原因となる。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;の終わり：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;!-- ページのコンテンツ --&amp;gt;
    &amp;lt;script src=&quot;./index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bodyでのDOM解析より前に走るため、HTMLの解析とページのレンダリングが完了してからスクリプトが実行されることになる。
ページを開いたときに描画が先にされるため、ユーザーから見たときにページ表示速度が早い。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;タグの最後尾に書くのが最適なのか？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;描画を第一に考えるのであれば、&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;タグの終わりに書くことが推奨されてる。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ただしDOMが描画される前に行いたい処理などがある場合
&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;タグ内で初期化処理を記載した方が、読み込み順序等を気にする必要がなくなるため
あえて&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;に記載する方法も良さそう。&lt;/p&gt;
&lt;p&gt;例えば&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ページ初期化系：ページロード前に特定の初期化を行う必要がある場合。&lt;/li&gt;
&lt;li&gt;重要な設定系：ユーザー認証やアクセス制御、グローバル設定の適用など、ページ全体に影響を与えたい処理。&lt;/li&gt;
&lt;li&gt;スタイルやレイアウトの調整系：ページロード後にレイアウトが崩れるのを防ぐために、事前にスタイルを適用する場合。
上記のような処理は、&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;タグ内に配置するのが良さそう。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個人的に以前行ってたのは以下みたいな処理
(他にもいろいろやり方はあるはずだが一例として、、、)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;script&amp;gt;
        // 認証状態
        let authenticated = false;
        // 認証関連の初期化処理....
    &amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;./script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;気を付けないといけない点として、DOMが解析される前に処理を行っているため
DOM関連の操作はここでは行えない。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;タグにスクリプトを記載しつつ描画を先に行いたい&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;の最後に記載する方法が良いとはいえ、
外部のスクリプトなどは&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;にあった方が何を使用しているか分かりやすかったりする。&lt;/p&gt;
&lt;p&gt;HTML5以上であればそんなときに使える&lt;code&gt;defer&lt;/code&gt;属性がある。
&lt;code&gt;defer&lt;/code&gt;属性を使用すると、スクリプトはHTMLの解析が完了する(正確にはDOMContentLoadedイベント直前)まで実行を遅延する。
また、外部スクリプト等の場合ダウンロードが発生するが
ダウンロードもDOMの解析と非同期で処理が行われるため、描画に対するブロッキングが発生しない。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遅延読み込み（defer属性）：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;script src=&quot;./index.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;async&lt;/code&gt;属性でも同じようなことができるが、
&lt;code&gt;async&lt;/code&gt;属性の場合、ダウンロードが非同期になるまでは同じで
スクリプトの実行がダウンロード完了次第になるためHTMLの解析と並行する。
そのため特定の順序でスクリプトを実行する必要がある場合には注意が必要。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;async&lt;/code&gt;属性と&lt;code&gt;defer&lt;/code&gt;属性の詳しい違いは以下のサイトがかなり詳しく分かりやすく紹介してくれているのでそちら参考。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://qiita.com/phanect/items/82c85ea4b8f9c373d684&quot;&gt;scriptタグに async / defer を付けた場合のタイミング&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>JavaのByteの計算で詰まった話</title><link>https://tech.storias-blog.com/blogs/stuck_on_java_byte_math/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/stuck_on_java_byte_math/</guid><description>Javaのbyteは符号付きのため0x80以上がマイナスになる。byte計算で嵌ったポイントを解説。</description><pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;入力されたbyteが0x00 ~ 0xFDの間にあるか確認したい&lt;/h2&gt;
&lt;p&gt;指定されたbyteが0x00 ~ 0xFDにいる事を確認したく以下コードを書いたとき&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;byte arg = (byte) 0x01;

if((byte) 0x00 &amp;gt;= arg &amp;amp;&amp;amp; arg =&amp;lt; (byte) 0xFD){
  System.out.println(&quot;問題なし&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;何故か条件に入らない、、、&lt;/p&gt;
&lt;h2&gt;Javaのbyteは符号付き&lt;/h2&gt;
&lt;p&gt;Javaのbyteは&lt;code&gt;符号付き&lt;/code&gt;のためbyteを数値として表すと
-128 ~ 127
になる。
つまり、2進数の&lt;code&gt;1000 0000&lt;/code&gt;で符号付きとして判断されるため
0x80 ~ 0xFFまではマイナスの値になる。&lt;/p&gt;
&lt;p&gt;なので&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(byte) 0x00 &amp;gt; (byte) 0xFF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;は&lt;code&gt;true&lt;/code&gt;になる。&lt;/p&gt;
&lt;h2&gt;どうしたか&lt;/h2&gt;
&lt;p&gt;intとして扱ってあげれば符号付きで変換されないので&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;byte arg = (byte) 0x01;

if(0x00 &amp;gt;= arg &amp;amp;&amp;amp; arg =&amp;lt; 0xFD){
  System.out.println(&quot;問題なし&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;としてあげれば、やりたいことができる。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;byte触る機会少なかったら気づかなかった...(言い訳)&lt;/p&gt;
&lt;h2&gt;補足：ビットマスク演算 (&lt;code&gt;arg &amp;amp; 0xFF&lt;/code&gt;) を使った無符号化処理&lt;/h2&gt;
&lt;p&gt;また、別の解決策として &lt;code&gt;&amp;amp; 0xFF&lt;/code&gt; によるビット演算を行い、&lt;code&gt;int&lt;/code&gt; 型にプロモーションする方法も一般的です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;byte arg = (byte) 0xFD; // 数値としては -3
int unsignedArg = arg &amp;amp; 0xFF; // 0x000000FD (数値としては 253)

if (unsignedArg &amp;gt;= 0x00 &amp;amp;&amp;amp; unsignedArg &amp;lt;= 0xFD) {
    System.out.println(&quot;0x00〜0xFDの範囲内です&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;arg &amp;amp; 0xFF&lt;/code&gt; を行うことで、符号拡張によって1で埋まった上位24ビットがゼロクリアされ、0〜255の無符号整数（unsigned byteと同等）として安全に比較・計算が行えるようになります。
パケット解析やバイナリプロトコルを扱う処理では定番の書き方なので、あわせて押さえておくと安心です。&lt;/p&gt;
</content:encoded></item><item><title>GASでHTMLを公開するときに詰まった個所まとめ</title><link>https://tech.storias-blog.com/blogs/gas_html/</link><guid isPermaLink="true">https://tech.storias-blog.com/blogs/gas_html/</guid><description>Google Apps ScriptでHTMLを公開する際に詰まったポイント。iframeのX-Frame-Optionsやスタイル適用などを解説。</description><pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GASでHTMLを静的に公開したい時に詰まった個所まとめ
&lt;strong&gt;GASを使用してWebサイトの公開を行う方法とかは色々な箇所で既に説明されているので飛ばします。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;iframe&lt;/h2&gt;
&lt;p&gt;ページ内に埋め込み系のコンテンツがある場合
GAS側では&lt;code&gt;X-Frame-Options&lt;/code&gt;に&lt;code&gt;SAMEORIGIN&lt;/code&gt;が指定されるためSameOriginPolicyで埋め込みがブロックされる。
そのため、&lt;code&gt;setXFrameOptionsMode&lt;/code&gt;を使ってポリシーの設定をする必要がある。
&lt;code&gt;setXFrameOptionsMode&lt;/code&gt;に&lt;code&gt;HtmlService.XFrameOptionsMode.ALLOWALL&lt;/code&gt;を渡す事ですべて許可扱いになり埋め込みがブロックされなくなる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function doGet() {
  return HtmlService.createHtmlOutputFromFile(&apos;index&apos;).setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning{name=&quot;&quot;}
&lt;code&gt;HtmlService.XFrameOptionsMode.ALLOWALL&lt;/code&gt;
を使用するとすべて許可になるため、iframeを自由に埋め込み表示することができるが
クリックジャッキングも行えることになるため、自身でセキュリティー対策を行う必要あり。
詳しくは公式を参照。
&lt;a href=&quot;https://developers.google.com/apps-script/reference/html/x-frame-options-mode?hl=ja&quot;&gt;Enum XFrameOptionsMode&lt;/a&gt;
:::&lt;/p&gt;
&lt;h2&gt;ページ同士のリンク&lt;/h2&gt;
&lt;p&gt;ページ同士のリンクは静的サイトと違いGASとして完結させる必要があるため
GAS側にスクリプトを書いて対応する必要がある。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function doGet(e) {
  let page = e.parameter.page;
  if (!page) {
    page = &apos;index&apos;;
  }
  return HtmlService.createTemplateFromFile(page).evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function getAppUrl() {
  return ScriptApp.getService().getUrl();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;変更前&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/index.html&quot;&amp;gt;ホーム&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;変更後&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;&amp;lt;?= getAppUrl() ?&amp;gt;?page=index&quot;&amp;gt;ホーム&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTMLファイルには&lt;code&gt;getAppUrl&lt;/code&gt;関数として、GASのURLを返す関数を作成しておき
GAS側でHTMLを返す際にURLを埋め込んで返す。
また、&lt;code&gt;doGet&lt;/code&gt;関数でリクエストパラメータを受け取れるようにし
指定されたファイルをHTMLとして返却する。&lt;/p&gt;
&lt;h2&gt;ページの埋め込み&lt;/h2&gt;
&lt;p&gt;ページが複数あって、HTML同士を共通化してある時
GAS側でスクリプトを書いてあげる必要がある。
例えば、&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;project-root/
└── public/
    ├── pages/
    │   └── navbar.html
    └── index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;:
&amp;lt;body&amp;gt;
  &amp;lt;nav id=&quot;navbar&quot;&amp;gt;&amp;lt;/nav&amp;gt;
&amp;lt;/body&amp;gt;
:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;の時
静的サイトの時は、jQueryなり、javascriptでDOMのコピーをすれば対応できるが
GASの場合だと取得する対象のHTMLが静的ファイルとして配置されないため
ファイルの中身が取得できない。&lt;/p&gt;
&lt;p&gt;そのため、ファイルの共通化をしたい場合は以下のGASを書いて対応させる必要あり。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function doGet(e) {
  let page = e.parameter.page;
  if (!page) {
    page = &apos;index&apos;;
  }
  return HtmlService.createTemplateFromFile(page).evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function include(filename) {
  return HtmlService.createTemplateFromFile(filename).evaluate().getContent();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML側には共通化HTMLを挿入したい箇所に以下の文を追加する。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;nav id=&quot;navbar&quot;&amp;gt;
    &amp;lt;?!= include(&apos;navbar&apos;) ?&amp;gt; &amp;lt;!-- ←この行を追加 --&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ポイント&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GASでHTMLの内容を返す時にevaluateを行ってからHTMLを返却しないとGASの埋め込みコード(*&lt;strong&gt;1&lt;/strong&gt;)が評価されなくなる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;*&lt;strong&gt;1&lt;/strong&gt; 例えば&lt;code&gt;&amp;lt;?= getAppUrl() ?&amp;gt;&lt;/code&gt;などの埋め込み部分のテンプレートに埋め込んでいるタグのこと&lt;/p&gt;
&lt;h2&gt;画像の埋め込み&lt;/h2&gt;
&lt;p&gt;画像ファイルを表示したい時、GASに画像ファイルをアップロードしても参照ができない。
そのため、Base64に変換して埋め込む必要がある。
Base64にするにはNodeで関数を記載するなり、gulpで埋め込むなりする方法がある。&lt;/p&gt;
&lt;h2&gt;GASの25MB制限&lt;/h2&gt;
&lt;p&gt;GASはすべてのファイル合わせて25MBの制限がある。
&lt;a href=&quot;https://developers.google.com/apps-script/guides/services/quotas?hl=ja#current_limitations&quot;&gt;公式&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;もし、画像などを25MB以上ページに使用したい場合は
Base64での埋め込みではなくGoogleDriveに画像を保存し
HTML側から取得する必要あり。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function getImageAsBase64(fileName) {
  const fileId = getFileIdByName(fileName);
  var file = DriveApp.getFileById(fileId);
  var blob = file.getBlob();
  var contentType = blob.getContentType();
  var base64Data = Utilities.base64Encode(blob.getBytes());
  return &apos;data:&apos; + contentType + &apos;;base64,&apos; + base64Data;
}

function getFileIdByName(fileName) {
  const folder = DriveApp.getFolderById(&quot;ここに画像フォルダのID&quot;);
  const files = folder.getFiles();
  let fileId = null;
  while (files.hasNext()) {
    const file = files.next();
    
    if (file.getName() === fileName) {
      fileId = file.getId();
      Logger.log(&quot;Found file: &quot; + fileName + &quot; with ID: &quot; + fileId);
      break;
    }
  }
  
  if (fileId === null) {
    Logger.log(&quot;File not found: &quot; + fileName);
  }
  return fileId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画像を取得するスクリプトを追加&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
function fetchImages() {
    const images = document.querySelectorAll(&apos;img[data-file-id]&apos;);
    images.forEach(function(img) {
      const fileId = img.getAttribute(&apos;data-file-id&apos;);
      google.script.run.withSuccessHandler(function(base64Image) {
        img.src = base64Image;
      }).getImageAsBase64(fileId);
    });
}
      
document.addEventListener(&apos;DOMContentLoaded&apos;, fetchImages);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画像ファイルを表示したい箇所に以下のように指定&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src data-file-id=&quot;画像ファイル名&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記でBase64として表示することができる。
もし、画像を誰でも共有にする場合は画像のURLを取得してimageタグのsrcに指定する方法もある。&lt;/p&gt;
</content:encoded></item></channel></rss>