Azure Synapse Spark で environment.yml を使うと LIBRARY_MANAGEMENT_FAILED になる原因と安全な対処法

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

ポイントは、con​da パッケージとして何かを指定していないことです。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 に持ち込むと安定します。

  1. ローカル環境で pip wheel パッケージ名 を実行し、.whl ファイルを作成する
  2. Synapse ワークスペースの「ワークスペース パッケージ」に .whl をアップロードする
  3. 対象の Spark プールにそのパッケージをアタッチする
  4. ノートブックでは environment.yml には書かず、プール側のライブラリとして利用する

このように「ビルドが重いものは Wheel で事前配布、その他は pip: セクションに記述」という役割分担をすると、セッション起動~クラスター作成の安定性が一気に上がります。

ステップ 4:パッケージは段階的に追加する

いきなり 20 個、30 個とパッケージを追加するのではなく、次のような段階的な追加が安全です。

  1. まずは最小構成(本当に必要な 3~5 パッケージ程度)で environment.yml を作成
  2. Synapse のノートブック セッションに適用し、クラスターが問題なく起動するか確認
  3. 動作確認できたら、1〜2 個ずつパッケージを追加しながら再度セッションを起動
  4. ある追加で急に LIBRARY_MANAGEMENT_FAILED が出た場合、その直前に追加したパッケージを疑う

「少しずつ追加して、どこで壊れるかを見つける」ことで、問題パッケージや過剰なバージョン指定を迅速に特定できます。

セッションでは動くのに、同じ environment.yml を Spark プールに適用すると動かない理由

次に、「セッションに適用した .yml は動作するのに、同じファイルを Spark プールに設定すると、ノートブックでパッケージに関するエラーが出る」という疑問を整理します。

セッション適用とプール適用の違い

両者の違いをざっくり表にすると次のようになります。

項目セッションへの environment.yml 適用Spark プールへの適用(requirements / Wheel)
適用範囲特定のノートブック セッションのみプール上のすべてのセッション
影響の強さベース環境に一時的に上書き(多少の不整合でも動いてしまうことがある)ランタイムの恒久構成を変更するため、既定ライブラリとの整合性が厳密に問われる
失敗時の影響そのセッションだけエラーになるプール全体の起動に失敗したり、他ノートブックにも影響する
デバッグのしやすさノートブック単位で試行錯誤しやすい設定変更→プール再起動が必要で、試行回数を稼ぎにくい

このように、セッション適用はやや「緩め」、プール適用はかなり「厳しめ」と考えるとイメージしやすくなります。同じ YAML でも、セッションではたまたま動いているだけで、プールレベルに持ち上げると Synapse ランタイムの既定依存と矛盾して破綻する、というケースが多く発生します。

実務フロー:まずセッションで安定構成を確定させてからプールに昇格

そこでおすすめの運用フローは次のようなステップです。

  1. 最小構成の environment.yml を使い、ノートブック セッションで動作検証する
  2. 問題なく動くようになったら、セッション内で pip freeze などを実行し、実際にインストールされた依存パッケージを確認する
  3. そこから、Synapse 既定で入っているものや開発用ライブラリを取り除き、最小限の依存関係だけを抜き出した requirements.txt を作る
  4. Spark プール側には、原則としてこの requirements.txt か、あるいは Wheel パッケージを使ってライブラリを適用する
  5. プールを再起動し、空のノートブックから 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 が発生してしまう場合は、ログから原因パッケージを特定します。大まかな手順は次の通りです。

  1. エラーになったノートブックの Spark ジョブ(アプリケーション)画面を開く
  2. ドライバー ノードのログを確認する
  3. LibraryManagement、pip install、conda などのキーワードで検索する
  4. どのパッケージのインストール中にエラーやタイムアウト、ビルド失敗のメッセージが出ているかを探す

よくあるパターンは次のようなものです。

  • 特定のパッケージのコンパイルが非常に長くかかり、タイムアウトしている
  • 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 パッケージ管理を設計する際は、ぜひこの考え方を前提に構成を見直してみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次