調べた内容を忘れそうなのでメモ。Hadoopのリリース元およびバージョンごとにあれこれとscribeから使えるかどうかの制約があるので、書き出してみる。
scribeから使えるHadoopのバージョン
Apacheリリース版 stable 0.20.2:ダメ
scribeをApacheリリース版の 0.20.2 (以下 0.20.2) の libhdfs/hdfs.h を参照しつつビルドしようとすると、以下のようなエラーが出る。
HdfsFile.cpp: In member function ‘void* HdfsFile::connectToPath(const char*)’: HdfsFile.cpp:227: error: ‘hdfsConnectNewInstance’ was not declared in this scope HdfsFile.cpp:255: error: ‘hdfsConnectNewInstance’ was not declared in this scope
これは以下のスレッドで議論されていた。
http://groups.google.com/group/scribe-server/browse_thread/thread/6a2133858789c5e0/6688ff95ccd85047
簡単に内容をまとめると「0.21 で採用されたAPIを使ってるから、0.21 もしくはAPIがバックポートされた Cloudera版のHadoopを使ってね」らしい。
確かに0.20.2の hdfs.h にこんな関数の定義はない。 0.21.0 にはある。
Apacheリリース版 unstable 0.21.0:ダメ
じゃあとApacheリリース版の 0.21.0 (以下 0.21.0) と組合せてビルドしようとすると、以下のようなエラーが出る。
/usr/local/hadoop/hdfs/src/c++/libhdfs/hdfs.h: In member function ‘virtual void HdfsFile::deleteFile()’: /usr/local/hadoop/hdfs/src/c++/libhdfs/hdfs.h:283: error: too few arguments to function ‘int hdfsDelete(void*, const char*, int)’ HdfsFile.cpp:147: error: at this point in file
なぜだ、と思って hdfs.h を確認する。0.21.0 での定義はこんな。 hdfs/src/c++/libhdfs/hdfs.h 275行目付近
int hdfsDelete(hdfsFS fs, const char* path, int recursive);
scribeの呼び出し側のコードはこんな。src/HdfsFile.cpp 145行目付近
void HdfsFile::deleteFile() { if (fileSys) { hdfsDelete(fileSys, filename.c_str()); } LOG_OPER("[hdfs] deleteFile %s", filename.c_str()); }
なんで2引数での呼び出しなの! と思ったら 0.20.2 のコードを見てみると2引数で宣言されていた。src/c++/libhdfs/hdfs.h 269行目付近
int hdfsDelete(hdfsFS fs, const char* path);
つまりこのAPIについては 0.20.2 のスタイルで呼び出している、と。ねえ、これ詰んでない?
Clouderaリリース版 CDH
で、CDHではそのあたりがうまく整合するようバックポートされているらしい。
というよりscribeはそもそもCDHだけをベースに書かれてて、Apache版は完全無視されているご様子。ひどす。
libhdfsが使用できるCDHのバージョン
CDH3 beta3 0.20.2+737:ダメ
ということで新しい方から試すぜ! とCDH3beta3の 0.20.2+737*1 を落としてきてscribeをビルドするとさくっと通る。ので起動してhdfsに接続させると、以下のようなエラーログが大量に吐かれて南無三。
[Wed Jan 5 20:42:19 2011] "[hdfs] ERROR: HDFS is not configured for file: hdfs://node01.hadoop-cluster:50071/scribe/HOGE/HOGE-2011-01-05_00000" [Wed Jan 5 20:42:19 2011] "[hdfs] Before hdfsConnectNewInstance(node01.hadoop-cluster, 50071)" could not find method newInstance from class org/apache/hadoop/fs/FileSystem with signature (Ljava/net/URI;Lorg/apache/hadoop/conf/Configuration;Ljava/lang/String;)Lorg/apache/hadoop/fs/FileSystem; Exception in thread "main" java.lang.NoSuchMethodError: newInstance Call to org.apache.hadoop.fs.Filesystem::newInstance(URI, Configuration) failed! [Wed Jan 5 20:42:19 2011] "[hdfs] After hdfsConnectNewInstance" [Wed Jan 5 20:42:19 2011] "[HOGE] Failed to open file <hdfs://node01.hadoop-cluster:50071/scribe/HOGE/HOGE-2011-01-05_00000> for writing" [Wed Jan 5 20:42:19 2011] "[HOGE] File failed to open FileStore::handleMessages()" [Wed Jan 5 20:42:19 2011] "[HOGE] WARNING: Re-queueing 42974 messages!"
エラーログがわかりにくい(JNIのせいだ)が、newInstance(URI, Configuration, String) を呼ぼうとして NoSuchMethodError になっているようだ。こういうエラーを眺めていると、静的言語だから云々とか言う人の顔をまじまじと3分くらい眺めてやりたい心境になる。
で、該当のコードはどこかなーと 0.20.2+737 から探してみる。まずは呼び出し側。src/c++/libhdfs/hdfs.c の361行目付近から。
else if (!strcmp(host, "default") && port == 0) { /* 中略 */ if (invokeMethod(env, &jVal, &jExc, STATIC, NULL, HADOOP_FS, "newInstance", JMETHOD3(JPARAM(JAVA_NET_URI), JPARAM(HADOOP_CONF), JPARAM(JAVA_STRING), JPARAM(HADOOP_FS)), jURI, jConfiguration, jUserString) != 0) { errno = errnoFromException(jExc, env, "org.apache.hadoop.fs." "FileSystem::newInstance"); goto done; } jFS = jVal.l; } else { /* 中略 */ if (invokeMethod(env, &jVal, &jExc, STATIC, NULL, HADOOP_FS, "newInstance", JMETHOD3(JPARAM(JAVA_NET_URI), JPARAM(HADOOP_CONF), JPARAM(JAVA_STRING), JPARAM(HADOOP_FS)), jURI, jConfiguration, jUserString) != 0) { errno = errnoFromException(jExc, env, "org.apache.hadoop.fs." "Filesystem::newInstance(URI, Configuration)"); goto done; } jFS = jVal.l; }
JNIがわかんなくても、なんとなくJavaのコードの "newInstance" を jURI, jConfiguration, jUserString を引数に呼んでるらしいのはわかる。invokeMethod関数を読めば呼び出し機構の詳細がわかるけど、ここでは割愛。
んでこれに対応する呼ばれるJava側のコード org.apache.hadoop.fs.Filesystem::newInstance を探す。src/core/org/apache/hadoop/fs/FileSystem.java の 230行目付近から。
public static FileSystem newInstance(URI uri, Configuration conf) throws IOException { String scheme = uri.getScheme(); String authority = uri.getAuthority(); if (scheme == null) { // no scheme: use default FS return newInstance(conf); } if (authority == null) { // no authority URI defaultUri = getDefaultUri(conf); if (scheme.equals(defaultUri.getScheme()) // if scheme matches default && defaultUri.getAuthority() != null) { // & default has authority return newInstance(defaultUri, conf); // return default } } return CACHE.getUnique(uri, conf); } /** Returns a unique configured filesystem implementation. * This always returns a new FileSystem object. */ public static FileSystem newInstance(Configuration conf) throws IOException { return newInstance(getDefaultUri(conf), conf); }
1引数と2引数しかないwwwおまww
で、もしやと思って 0.21.0 のコードを確認してみると、3引数のものがあった。あーあ。libhdfsの側だけバックポートしてJavaのコードの側はバックポートし忘れてるらしい。何やってんの。ていうかテストしてねーんじゃん。信頼性を向上させるパッチとかどこの口のセリフですか。笑い死にさせる気か。
で、問題に目処がついてから調べてみたら、ひっかかった。
https://groups.google.com/a/cloudera.org/group/cdh-dev/browse_thread/thread/17d9544e762bf063
10月末に報告されてから2ヶ月以上放置かあ。ひどい話だね。
CDH3 beta2 0.20.2+320:大丈夫!
ということで CDH3 beta2 リリースの 0.20.2+320 を眺めてみると、以下のような感じ。src/c++/libhdfs/hdfs.c の499行目付近より。
else if (!strcmp(host, "default") && port == 0) { //fs = FileSystem::get(conf); if (invokeMethod(env, &jVal, &jExc, STATIC, NULL, HADOOP_FS, "newInstance", JMETHOD1(JPARAM(HADOOP_CONF), JPARAM(HADOOP_FS)), jConfiguration) != 0) { errno = errnoFromException(jExc, env, "org.apache.hadoop.fs." "FileSystem::newInstance"); goto done; } jFS = jVal.l; } else { // fs = FileSystem::newInstance(URI, conf); /* 中略 */ if (invokeMethod(env, &jVal, &jExc, STATIC, NULL, HADOOP_FS, "newInstance", JMETHOD2(JPARAM(JAVA_NET_URI), JPARAM(HADOOP_CONF), JPARAM(HADOOP_FS)), jURI, jConfiguration) != 0) { errno = errnoFromException(jExc, env, "org.apache.hadoop.fs." "Filesystem::newInstance(URI, Configuration)"); goto done; } jFS = jVal.l; }
わーい2引数呼び出しだよヤッタネー。ということでビルドして動作させてみたら動きました! おつかれさま!
*1:この+NってのはClouderaが当てたパッチの数らしい