Azure Synapse Analytics の Spark プールに Python パッケージをまとめて入れようとして、手元の Anaconda 環境から出力した environment.yml をアップロードしたら、セッション起動時に LIBRARY_MANAGEMENT_FAILED エラーで落ちてしまう――そんな経験をするケースは少なくありません。本記事では、この現象の背景と原因、そして「素早く・確実に必要パッケージだけを入れる」ための現実的な運用パターンを、実務ベースで詳しく解説します。
Synapse Spark セッションに environment.yml を適用すると LIBRARY_MANAGEMENT_FAILED になる理由
Azure Synapse Analytics では、Spark プールに対して次のような手段で Python パッケージを追加できます。
- ノートブック セッションに対する一時的な
environment.yml(もしくはrequirements.txt)の適用 - Spark プールの構成としてのライブラリ設定(プール全体に恒久反映)
- ワークスペース パッケージ(Wheel など)をアップロードしてプールにアタッチ
このうち「ノートブック セッションに environment.yml をアップロードして適用」する機能は一見便利ですが、Anaconda のフル環境をそのままエクスポートした YAML を使うと高い確率で失敗します。典型的な症状は次のとおりです。
- セッション起動時に
LIBRARY_MANAGEMENT_FAILED(Livy セッション Error)と表示される - Spark アプリケーションのログを見ると、依存解決やパッケージインストールの途中でタイムアウトしている
- クラスターが何度か再作成・再試行されるが、最終的にセッションが失敗した状態でノートブックが止まる
原因は大きく言えば、「Synapse のランタイムに対して、export した環境が過剰で、かつ OS やバージョンの条件が合っていない」ことに尽きます。
結論:environment.yml は「最小構成+pip セクションだけ」で運用する
実務的な解決策として最も安定するのは、次の方針です。
- YAML を最小限に削る(必要なパッケージだけを列挙する)
- 実質的には「
requirements.txtを YAML のpip:セクションに書く」イメージで運用する - OS 依存の conda パッケージや Jupyter 系パッケージは一切含めない
最小構成の environment.yml 例は次のようになります。
name: synapse-session
dependencies:
- pip
- pip:
- flair==0.10
- transformers==4.11.2
- torch==1.9.1 # ランタイムに合わせて調整
- sentencepiece==0.1.95
- regex==2021.9.30
- tqdm==4.62.3
ポイントは、conda パッケージとして何かを指定していないことです。dependencies: のトップレベルは pip だけにして、その下の pip: に「本当に必要なパッケージ」だけを書きます。
Synapse Spark のライブラリ管理の仕組みをざっくり理解する
なぜフル環境の environment.yml をそのまま適用すると失敗しやすいのか、背景を簡単に整理しておきます。
- Synapse の Spark プールはLinux ベースコンテナで動作する
- Synapse ランタイム自体が、すでに多くの Python ライブラリを含んだ「ベース環境」を持っている
- ユーザーがアップロードした
environment.ymlは、そのベース環境の上から追加&一部上書きするイメージで適用される - このとき OS 依存・ビルド番号付きの conda パッケージや、ランタイムと整合しないバージョン指定があると依存解決が破綻する
特に注意が必要なのが次のようなケースです。
| 原因パターン | 典型的な記述・パッケージ | Synapse での問題 |
|---|---|---|
| OS 非互換のパッケージ | pywin32、winpty、vs2015_runtime、m2w64-* など | Synapse は Linux ベースのためインストールできず失敗 |
| 過剰なフルエクスポート | channels: や prefix:、Jupyter 系ライブラリ一式 | 環境全体を上書きしようとして依存が衝突・インストールに時間がかかる |
| ビルド番号まで固定 | numpy=1.21.2=py38h2bbff1b_1 のような指定 | Synapse 既定のバージョンと整合できず解決に失敗する |
| ネイティブビルドが必要 | fasttext、hdbscan、tokenizers 等 | ビルド環境や時間制限のためインストールに失敗しやすい |
| パッケージ点数が多すぎる | 数百個以上のライブラリを一度に追加 | 依存解決とダウンロードで時間切れになり、LIBRARY_MANAGEMENT_FAILED になる |
失敗しやすい environment.yml の典型例
例えば、手元の Anaconda 環境で次のようなコマンドを実行してエクスポートするとします。
conda env export > environment.yml
このとき生成される YAML には、次のような情報が大量に含まれます。
name:(ローカル環境の名前)channels:(defaults、conda-forgeなど)prefix:(ローカル PC 上のパス)- OS 依存のパッケージ(Win 専用・Linux 専用など)
- Python 本体や Jupyter 関連パッケージ一式
- それぞれのパッケージのビルド番号(
=py38h2bbff1b_1のような指定)
これをそのまま Synapse に持ち込むと、Synapse ランタイム側が持っている Python や標準ライブラリ群と激しく衝突します。その結果、ライブラリ管理プロセスのどこかで失敗し、最終的に LIBRARY_MANAGEMENT_FAILED で終わる、という流れです。
最小構成の environment.yml を作る手順(セッション用ベストプラクティス)
ここからは、「セッションに対して素早く・確実にパッケージを入れる」ための、具体的な手順を整理します。キーワードは最小構成とpip セクションです。
ステップ 1:YAML から不要情報を徹底的に削る
まず、手元の environment.yml がある場合は、次の項目をすべて削除します。
prefix:(ローカルパスなので必ず削除)channels:(Synapse 側の設定と干渉しないよう原則削除)- Python 本体の指定(例:
python=3.8.10) - Jupyter / 開発系パッケージ(
notebook、jupyter_*、ipykernel等) - Windows 専用パッケージ(
pywin32、pywinpty、winpty、vs2015_runtime、m2w64-*など) - ビルド番号付きの conda パッケージ(
numpy=1.21.2=py38h...のような書き方)
YAML の骨格としては、次のような非常にシンプルな形を目指します。
name: synapse-session
dependencies:
- pip
- pip:
- (ここに必要なパッケージだけを書く)
ステップ 2:本当に必要なパッケージだけを列挙する
次に、実際に使いたいライブラリだけをピックアップして pip: に書きます。例えば、自然言語処理に flair と transformers を使うのであれば、関連ライブラリを含めて次のように指定します。
name: synapse-session
dependencies:
- pip
- pip:
- flair==0.10
- transformers==4.11.2
- torch==1.9.1
- sentencepiece==0.1.95
- regex==2021.9.30
- tqdm==4.62.3
このときのポイントは次の通りです。
- バージョン固定は「必要最小限」に留める(互換性が必要なライブラリのみピン留め)
numpy、pandas、pyarrow、scipy、torchなど Synapse が標準で持っている/強く依存しているライブラリは、可能な限りピン留めしない- どうしてもバージョンを制約したい場合は、
>=, <を使って範囲指定にする(例:numpy>=1.20,<1.23)
ステップ 3:ネイティブビルドが必要なライブラリは別扱いにする
fasttext、hdbscan、sentencepiece、lxml、tokenizers など、C/C++ コンパイルを伴うライブラリは、Synapse のセッション起動時にコンパイルが走ると時間切れやビルドエラーで失敗しやすいです。
こうしたライブラリは、可能であればあらかじめ手元で Wheel をビルドしておき、次のような方法で Synapse に持ち込むと安定します。
- ローカル環境で
pip wheel パッケージ名を実行し、.whlファイルを作成する - Synapse ワークスペースの「ワークスペース パッケージ」に
.whlをアップロードする - 対象の Spark プールにそのパッケージをアタッチする
- ノートブックでは environment.yml には書かず、プール側のライブラリとして利用する
このように「ビルドが重いものは Wheel で事前配布、その他は pip: セクションに記述」という役割分担をすると、セッション起動~クラスター作成の安定性が一気に上がります。
ステップ 4:パッケージは段階的に追加する
いきなり 20 個、30 個とパッケージを追加するのではなく、次のような段階的な追加が安全です。
- まずは最小構成(本当に必要な 3~5 パッケージ程度)で environment.yml を作成
- Synapse のノートブック セッションに適用し、クラスターが問題なく起動するか確認
- 動作確認できたら、1〜2 個ずつパッケージを追加しながら再度セッションを起動
- ある追加で急に LIBRARY_MANAGEMENT_FAILED が出た場合、その直前に追加したパッケージを疑う
「少しずつ追加して、どこで壊れるかを見つける」ことで、問題パッケージや過剰なバージョン指定を迅速に特定できます。
セッションでは動くのに、同じ environment.yml を Spark プールに適用すると動かない理由
次に、「セッションに適用した .yml は動作するのに、同じファイルを Spark プールに設定すると、ノートブックでパッケージに関するエラーが出る」という疑問を整理します。
セッション適用とプール適用の違い
両者の違いをざっくり表にすると次のようになります。
| 項目 | セッションへの environment.yml 適用 | Spark プールへの適用(requirements / Wheel) |
|---|---|---|
| 適用範囲 | 特定のノートブック セッションのみ | プール上のすべてのセッション |
| 影響の強さ | ベース環境に一時的に上書き(多少の不整合でも動いてしまうことがある) | ランタイムの恒久構成を変更するため、既定ライブラリとの整合性が厳密に問われる |
| 失敗時の影響 | そのセッションだけエラーになる | プール全体の起動に失敗したり、他ノートブックにも影響する |
| デバッグのしやすさ | ノートブック単位で試行錯誤しやすい | 設定変更→プール再起動が必要で、試行回数を稼ぎにくい |
このように、セッション適用はやや「緩め」、プール適用はかなり「厳しめ」と考えるとイメージしやすくなります。同じ YAML でも、セッションではたまたま動いているだけで、プールレベルに持ち上げると Synapse ランタイムの既定依存と矛盾して破綻する、というケースが多く発生します。
実務フロー:まずセッションで安定構成を確定させてからプールに昇格
そこでおすすめの運用フローは次のようなステップです。
- 最小構成の
environment.ymlを使い、ノートブック セッションで動作検証する - 問題なく動くようになったら、セッション内で
pip freezeなどを実行し、実際にインストールされた依存パッケージを確認する - そこから、Synapse 既定で入っているものや開発用ライブラリを取り除き、最小限の依存関係だけを抜き出した
requirements.txtを作る - Spark プール側には、原則としてこの
requirements.txtか、あるいは Wheel パッケージを使ってライブラリを適用する - プールを再起動し、空のノートブックから
importテストを行う
プールに適用する requirements.txt は、例えば次のようになります。
flair==0.10
transformers==4.11.2
torch==1.9.1
sentencepiece==0.1.95
regex==2021.9.30
tqdm==4.62.3
ここでも、OS 依存や Jupyter 関連、ビルド番号付きの依存は一切入れない、という方針を守ることが重要です。
失敗しやすい記述・パッケージのチェックリスト
environment.yml や requirements.txt を見直す際に、次のチェックリストを上から順に確認すると効率的です。
| チェック項目 | 具体例 | 対処方針 |
|---|---|---|
prefix: を入れていないか | prefix: C:\Users\... | 必ず削除する(Synapse では意味がない) |
channels: を指定していないか | channels: [defaults, conda-forge] | 原則削除する(必要な場合でも最小構成を優先) |
| Python 本体を固定していないか | python=3.8.10 など | Synapse ランタイムに任せ、YAML では指定しない |
| Windows 専用パッケージが入っていないか | pywin32、winpty、wincertstore、vs2015_runtime 等 | すべて削除する(Linux で動作しない) |
| Jupyter / 開発系が含まれていないか | notebook、jupyter_*、ipykernel 等 | クラスターには不要なので削除 |
| ビルド番号まで固定していないか | numpy=1.21.2=py38h... | バージョン指定が必要なら == か 範囲指定のみにする |
| ネイティブビルド要のパッケージがないか | fasttext、hdbscan、lxml、tokenizers 等 | 可能であれば Wheel 化してワークスペース パッケージで配布 |
| コアライブラリを強くピン留めしていないか | numpy==1.21.2、pandas==1.3.3 等 | Synapse が標準で持つものは極力ピン留めしない、または範囲指定にとどめる |
ログから「どのパッケージで落ちたか」を調べるコツ
それでも LIBRARY_MANAGEMENT_FAILED が発生してしまう場合は、ログから原因パッケージを特定します。大まかな手順は次の通りです。
- エラーになったノートブックの Spark ジョブ(アプリケーション)画面を開く
- ドライバー ノードのログを確認する
LibraryManagement、pip install、condaなどのキーワードで検索する- どのパッケージのインストール中にエラーやタイムアウト、ビルド失敗のメッセージが出ているかを探す
よくあるパターンは次のようなものです。
- 特定のパッケージのコンパイルが非常に長くかかり、タイムアウトしている
- OS 依存のライブラリが見つからない、というメッセージが出ている
- 既にインストールされているバージョンと指定バージョンが衝突している
ログで問題のパッケージが分かったら、environment.yml からそのパッケージをいったん削除するか、バージョン指定を緩める方向で修正し、再度セッションを起動して確認します。
運用テクニック:Synapse でのパッケージ管理パターンを分けて考える
現場では、次のように用途別にパッケージ管理パターンを分けておくと、トラブルを減らせます。
| 用途 | おすすめの管理方法 | 具体例 |
|---|---|---|
| 試行錯誤・PoC 用 | ノートブック セッションに最小構成の environment.yml を適用 | 新しい NLP ライブラリの評価、バージョン比較など |
| 本番バッチ ジョブ共通 | Spark プールに requirements.txt と Wheel を組み合わせて適用 | 定期バッチで共通利用する ETL ライブラリ群 |
| 一部ジョブだけで使う重いライブラリ | ワークスペース パッケージ(Wheel)を特定プールにのみアタッチ | GPU や大規模モデルを使うジョブ向けライブラリ |
特に、本番系の Spark プールは「できるだけシンプルに保つ」ことが重要です。遊び半分で追加したライブラリや、不要になった依存がそのまま残っていると、将来的なアップグレードやランタイム変更時に大きな障害の原因になりがちです。
まとめ:environment.yml は「requirements.txt を包む薄いラッパ」として使う
ここまでの内容を整理すると、Azure Synapse Analytics の Spark プールで environment.yml を扱う際のポイントは次のようになります。
- 原因:Anaconda のフル環境をそのまま export した
environment.ymlを適用すると、OS/ランタイム非互換・依存衝突・ビルド失敗・時間切れなどでLIBRARY_MANAGEMENT_FAILEDになりやすい - 解決策:YAML を最小構成に削り、実質的に「
requirements.txtをpip:セクションに書いたもの」として運用する - OS 依存パッケージや Jupyter/開発系パッケージ、ビルド番号付きの conda パッケージは すべて除外する
- ビルドが重い・ホイールが無いライブラリは、可能な限り Wheel を事前用意してワークスペース パッケージで配布し、environment.yml には含めない
- セッションで安定構成を作ってから、最小限の依存のみを抽出して Spark プールに昇格させる、という二段構えが安全
- 問題発生時はログから「どのパッケージで落ちているか」を特定し、そのパッケージを中心に YAML / requirements を見直す
「environment.yml をそのまま本番環境の完全スナップショットとして持ち込む」のではなく、Synapse 用にチューニングしたミニマルな依存リストとして扱うことで、LIBRARY_MANAGEMENT_FAILED に悩まされる時間を大幅に減らすことができます。これから Azure Synapse Analytics の Spark プールで Python パッケージ管理を設計する際は、ぜひこの考え方を前提に構成を見直してみてください。

コメント