libhdfsを使う

libhdfsのAPIは$(HADOOP_HOME)/libhdfs/docs/apiに詳しいドキュメントがあるので、そこを見ながらやると簡単です。サンプルが必要ないくらいに色々書いてあります。ただちょっと良く分からなかった点があったので、いくつかメモを残しておきます。

Connect, Disconnect

HDFSへはhdfsConnectを使って接続します。この関数にはhost,portを与えます。サンプルプログラムでは"default",0を指定していますが、このように指定するとローカルファイルシステムが使用されました。HDFSのhost,portを指定したらちゃんとHDFSが使用されました。

hdfsConnectで作成したhdfsFSはhdfsDisconnectで解放します。

相対パスの扱われ方

libhdfsで関数に相対パスを与えると、デフォルトでは/user/ユーザー名/からの相対パスとして解釈されるようです。この挙動はhdfsSetWorkingDirectoryという関数で変更可能です。現在のWorkingDirectoryはhdfsGetWorkingDirectoryで取得可能です。

hdfsSetWorkingDirectory(fs, "");

のように空のものを指定すると次のようなエラーメッセージをstderrに吐きました。

Exception in thread "main" java.lang.IllegalArgumentException: Can not create a Path from an empty string
        at org.apache.hadoop.fs.Path.checkPathArg(Path.java:82)
        at org.apache.hadoop.fs.Path.<init>(Path.java:90)
Can't construct instance of class org.apache.hadoop.fs.Path for

エラーメッセージは出力されましたが、hdfsSetWorkingDirectoryは普通に-1を返すだけでプログラムは終了しませんでした。その後fsを使いまわしても特になんの問題もありませんでした。

hdfsFileInfo

hdfsGetPathInfoやhdfsListDirectoryが返すhdfsFileInfoの中身が謎だったので調査してみました。mKind以外は名前通りなので省略。

mKindは良く分かりませんが、ファイルだと70、ディレクトリだと68でした。二進法に直すとそれぞれ1000110と1000100です。謎。ドキュメントにはhdfsFileInfoについて詳しくかかれていません。mKindはtObjectKind型(enum)らしいです。tObjectKindについてドキュメントには

typedef enum tObjectKind tObjectKind

port

ソースを読めという暗黙的ですが親切なメッセージが書いてあります。見てみました。

    typedef enum tObjectKind {
        kObjectKindFile = 'F',
        kObjectKindDirectory = 'D',
    } tObjectKind;

/(^o^)\

さっきのportという謎のメッセージは、下のように書いたせいで出力されていたようです。

    typedef int32_t   tSize; /// size of data for read/write io ops             
    typedef time_t    tTime; /// time type                                      
    typedef int64_t   tOffset;/// offset within the file                        
    typedef uint16_t  tPort; /// port                                           
    typedef enum tObjectKind {
        kObjectKindFile = 'F',
        kObjectKindDirectory = 'D',
    } tObjectKind;

doxygenコメントって、"//!<"こう書かないと次の行に対するコメントになってしまうのでは。

ちなみに、mNameは次のようなhdfs://から始まる絶対パスでした。

hdfs://localhost:50000/user/nobu/test/test.txt

ファイルへの書き込み

hdfsOpenFileでファイルを開いてそれに書き込む形になります。

hdfsFile hdfsOpenFile(hdfsFS fs,
                      const char* path,
                      int flags,
                      int bufferSize,
                      short replication,
                      tSize blocksize
                      )  	

これは通常のファイル関係のAPIと同じで、ファイル名や読み書きフラグなどを引数に与えます。それに加えてHDFS上でのブロックサイズやレプリケーション数なども与えることができるようです。この辺は0を指定するとデフォルトの値が使用されます。

そんで読み書きフラグがちょっと気になったのでhdfs.hを見てみたのですが、

#ifndef O_RDONLY
#define O_RDONLY 1
#endif

#ifndef O_WRONLY
#define O_WRONLY 2
#endif

となっていました。fcntl.hでは

#define O_RDONLY             00
#define O_WRONLY             01

こうなってます。これはさすがにヤバいですね…。既に問題になっていたようです。でも、hdfs.hではfcntl.hを先にincludeしているので、LinuxではO_RDONLYは0, O_WRONLYは1になるそうです。それもそれでヤバいのではw まあLinuxだけで使っている分には大丈夫そうですね。とりあえず下手に自分でaliasなどを作らない方が良さそうです。

それでは本題のファイル書き込み。hdfsWriteを使って書き込みます。書き込んだ結果はhdfsFlushかhdfsCloseを呼び出すと反映されます。既存のファイルを書き込みモードで開くとO_TRUNC的な挙動をします。追記はできません。追記はできませんが、hdfsWriteの呼び出しは何度かに分けて行うことができます。

hdfsWrite(fs, file, "test\n", 5);
hdfsWrite(fs, file, "test2\n", 6);
hdfsCloseFile(fs, file);

次のように、hdfsFlushを複数回呼んでも大丈夫でした。

hdfsWrite(fs, file, "test\n", 5);
hdfsFlush(fs, file);
hdfsWrite(fs, file, "test2\n", 6);
hdfsFlush(fs, file);
hdfsCloseFile(fs, file);

さらに、hdfsCloseFileを呼び出さずにプログラムを終了しても大丈夫でした(絶対やらないけど)。さすが。追記ができないのであまり嬉しくはないですが、書き込み途中に不慮の事故などでプログラムが落ちてしまっても大丈夫そうです。

追記

正確には、HDFS上へ変更が反映されるのはhdfsCloseFileを呼び出したときだけかもしれないです。詳しくは分からず。サーセン。とりあえず書き込み中に情報を取得したりすることはあまりないと思うので大丈夫かな。