Chapter 03

Nixがなぜ必要なのか

依存関係の問題[1]

パッケージの依存関係の管理はパッケージマネージャの重要な役割であり、同時に最も問題を引き起こしやすい部分です。

依存関係の衝突

依存関係に起因する様々な問題は、その厄介さから俗に依存関係地獄Dependency Hell)と呼ばれています[2]。ここではその内の1つ、依存関係の衝突を取り上げます。

こんな状態を想像してみましょう。

アプリケーションAとアプリケーションBがそれぞれ共有ライブラリCに依存していますが、AはCのバージョン1.0を、Bはバージョン2.0を必要としています。ここで、共有ライブラリCのバージョン1.0と2.0の間に互換性がない場合、AまたはBのいずれかの挙動が変わったり、最悪の場合は動作しなくなる可能性があります。基本的に同一パッケージの異なるバージョンを共存させることは困難であり、依存関係の解決が極めて複雑になります。

また、共有ライブラリCの変更はアプリケーションA, Bだけでなく、依存している全てのパッケージに影響を及ぼす可能性があります。

これはパッケージの依存関係における共有可変状態が引き起こす問題と言えます。共有可変状態は変更の影響範囲の推測を困難にし、様々な競合を引き起こします。パッケージマネージャに限らず、あらゆるシステムにおいて可能な限り回避すべき状態です。

暗黙的依存

パッケージの依存関係は、パッケージ定義に記述された依存パッケージをパッケージマネージャが自動的に取得することで解決されます。しかし、ほとんどのパッケージマネージャは依存関係を完全に追跡することができません。

パッケージのビルドを試みたところ、とある依存パッケージがインストールされていなかったため失敗した、という経験はありませんか?パッケージ定義における依存関係の指定が不足していたり、そのパッケージマネージャがカバーするエコシステムの範囲外の依存関係が存在したりすると、パッケージマネージャはそれを自動的に解決することができず、ユーザーが手動で解決しなければいけません。

多くのパッケージはインストール先の環境に特定のパッケージがインストールされていることを勝手に期待しています。例えば、Goのような静的リンクを行う言語でもない限り、ほとんどのプログラムはlibcに依存していますが、それをわざわざパッケージ定義で依存関係として指定することはほとんどないでしょう。「先にxxxをインストールしておいてください」と、最初からインスールに手動の依存解決を要求するソフトウェアも珍しくありません。

再現性の課題

前述の依存関係に関する問題は、現代の巨大で複雑なパッケージ管理において更に深刻な問題となります。そのため、パッケージマネージャは複雑化するソフトウェア開発の要請に応えてその機能を進化させてきました。

しかし、ツールが高機能化してもなお再現性の問題を解決しきることはできていません。モダンなプログラミング言語のパッケージマネージャは依存関係地獄に陥らない工夫がなされていますが、ほとんどのOSのパッケージマネージャはそのような設計にはなっていません。また、暗黙的依存については開発者の注意に頼らざるを得ないのが現状です。

この問題をパッケージマネージャの外側から解決する仕組みとしてDockerがあります。Dockerもといコンテナ仮想化技術は、開発環境構築、ビルド、デプロイに革新をもたらしました。アプリケーションからミドルウェアに至るまでを丸ごと1つのコンテナに固めることで再現性を大幅に向上させました。

ですが、Dockerは基本的に実行環境として運用されるものです。Flatpakのようなコンテナでアプリケーションを管理し実行する技術もありますが、やはり全てのソフトウェアを仮想化することは現実的ではありません。また、Dockerは環境の分離という点では優れていますが、再現性の面では不完全です。Dockerを用いても内部で動作するソフトウェアの依存関係を解決するのは従来型のパッケージマネージャだからです。

Nixという解決策

この世で最も完全に近い形で前述の問題を解決できる技術がNixです。

  1. 依存関係の衝突が原理的に発生しない
  2. 暗黙的依存がないことを機械的に保証

これらは決して魔法ではなく、その秘密はビルドシステムに隠されています。Nixはパッケージのビルドに非常に強い制約を課します。Nixは厳しい戒律で己を律することでDependency Hellからやって来る悪魔に真正面から立ち向かうのです。

脚注
  1. 1. Introduction - Nix Reference Manual ↩︎

  2. Dependency hell - Wikipedia ↩︎