Spark 実行時の Snappy *.so リンクエラーの対処法


Apache Spark で "failed to map segment from shared object: Operation not permitted." または「共有オブジェクトのセグメントをマップするのに失敗しました: 許可されていない操作です」と出る場合の対処方法について (この記事の内容は Spark 1.5.2, 1.6.1 + Ubuntu 14.04 + Java 1.8.0_60 での動作に基づいています)。

原因は Spark で使用している Snappy が Shard Object ファイルを /tmp にデプロイしダイナミックリンクを試みているため。/tmp という誰でも書き込めて (常識的には) 実行ファイルが置かれることのない場所の *.so ファイルへの動的リンクが実行環境のセキュリティに引っかかって失敗しています。セキュリティ上の脆弱性となるため多くの環境では制約が設けられていますよね。

JavaVM のケースでこの影響を受けるのは以下の 3 点ですのでシステムの構成を確認してください。システムのセキュリティポリシーが許すのであればこれらを無効化するなり許可するなりしておくのが楽です。

  1. SELinux
  2. Java Security Policy ($JAVA_HOME/jre/lib/security.policy)
  3. /tmp mount 時に noexec オプションが指定されている

もしこれらが変更できないのであれば JavaVM のシステムプロパティを指定して /tmp ではなく別のディレクトリを使用するように指定します。具体的には以下のプロパティのどちらかに *.so ファイルをロード可能なディレクトリを指定してください。

  1. org.xerial.snappy.tempdir
  2. java.io.tmpdir

後者は Spark そのものが使用する一時ディレクトリにも影響を与えることに注意。可能であれば前者のシステムプロパティで回避してください。

Spark クラスタ環境 (spark://~) では Master/Worker 共にこのシステムプロパティを指定します。

# $SPARK_HOME/conf/spark-env.sh
SPARK_DAEMON_JAVA_OPTS=-Dorg.xerial.snappy.tempdir=/dir/to/permitted/tmp

spark-shell などのインタラクティブシェルでは最初に:

System.setProperty("org.xerial.snappy.tempdir", "/dir/to/permitted/tmp")

を実行することでも Snappy が使用するディレクトリを変更できます。

↓問題発生時のスタックトレース (ggrks用)。

ava.lang.IllegalArgumentException: java.lang.UnsatisfiedLinkError: /tmp/snappy-unknown-074c81a0-85d8-4040-a9cc-d14fd00e247f-libsnappyjava.so: /tmp/snappy-unknown-074c81a0-85d8-4040-a9cc-d14fd00e247f-libsnappyjava.so: 共有オブジェクトのセグメントをマップするのに失敗しました: 許可されていない操作です
        at org.apache.spark.io.SnappyCompressionCodec.<init>(CompressionCodec.scala:151)
...
Caused by: java.lang.UnsatisfiedLinkError: /tmp/snappy-unknown-074c81a0-85d8-4040-a9cc-d14fd00e247f-libsnappyjava.so: /tmp/snappy-unknown-074c81a0-85d8-4040-a9cc-d14fd00e247f-libsnappyjava.so: 共有オブジェクトのセグメントをマップするのに失敗しました: 許可 されていない操作です
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)

同じ状況でCassandra でも同じ問題が発生するらしい。