2015年11月18日水曜日

impylaでqmark置換がうまく動かない

データベースで遊ぶならImpalaどうですか?と言われ使ってみることに。

いずれはPythonでいろいろするつもりだったので、Clouderaが提供しているPythonバインディングであるimpylaモジュールの使い勝手の確認も兼ね、手元のSqlite3データベースから行単位でレコードをコピーしていくPythonプログラムを書いてみました。ちなみに全レコードを単に載せ替えるだけなら、CSVなりに変換して直接HDFSからインポートする方が100万倍(未計測)速いです。

テーブルの構成は単純で、(INT, TIMESTAMP, STRING, STRING) の4カラムのみです。ところが、このレコードをimpyla経由で追加しようとしてもどうしてもうまくいかない。

cursor.execute('insert into table1 values (?,?,?,?)', (intvalue1, datetimevalue1, stringvalue1, stringvalue2))

どこにも間違う要素のない処理なのですが、必ずエラーが発生してしまいます。調べてみると、Impalaへ投げるSQL文を構築する途中で、datetimevalue1で置換されるべき部分が想定しない値になっていることが判明しました。

impylaモジュールは、他のPython用データベースアクセスモジュールと同様、Python DB-API 2.0に準拠しています。execute()の第一引数で渡したSQL文の?が第二引数で渡した値に置き換えられる、という部分はその仕様の一部であり、qmark paramstyleと呼ばれる書式です。SQL文の文字列置換の方式には幾つかあり、%sで指定した場所が置き換えられるformat paramstyle、:1:2など第二引数の位置に応じて置き換えられるnumbered paramstyle、:nameと指定してnameをキーとする辞書を第二引数に渡すnamed paramstyleなどがあります。(もう一つ、pyformatという書式がありますが、これはimpylaでは対応していません)

qmark、format、numbered paramstyleでの置換は、impyla/interfaces.py_replace_numeric_markers()関数で実装されていますが、ここが問題でした。元の関数には以下のような記述があります。

operation = replace_markers('?', operation, string_parameters)
operation = replace_markers(r'%s', operation, string_parameters)
for index in range(len(string_parameters), 0, -1):
    operation = operation.replace(':' + str(index),
                                  string_parameters[index - 1])

operationにはexecute()の第一引数の内容が、string_parametersには第二引数で指定されたタプルを文字列リストに変換した情報が入っています。

?で指定された部分を置き換え、続いて%sで指定された部分を置き換え、さらに:数値で指定された部分を置き換える、という処理を意図したものだと思われますが、明らかに変です。これでは、置換結果に別の書式の置換マークが含まれていた場合に正しい結果が得られません。例えば、qmarkで置換した文字列に%s:1などの部分文字列が含まれていると、その後の置換処理で意図しない置換が起きてしまいます。僕のコードが正しく動作しなかったのも、時刻文字列に:数値が含まれていたため、時刻の一部が再置換されてしまったことが理由でした。

_replace_numeric_markers()にはもう一つ問題があります。関数内部で入れ子定義されているreplace_markers()関数の処理はおおよそ以下のようになっています。

def replace_markers(marker, op, parameters):
    marker_index = 0
    while op.find(marker) > -1:
        op = op.replace(marker, parameters[marker_index], 1)
        marker_index += 1

これも場合によっては正しく動作しません。置換マークをfind()メソッドで検索しているため、置き換えた後の文字列に、置換マークが含まれていると再度マッチしてしまうためです。

上記の問題を修正するためのパッチを作ってpull requestしてみました。改善されるといいなぁ。

ちなみに、named paramstyleを用いた場合は別の関数で処理されるようになっており、そちらは問題なさそうです。

2015年1月22日木曜日

Python SimpleXMLRPCServerでHTTP/1.1による永続接続

え?まだ1.1使ってるの?いやいやその前に今時XMLRPCなの?という本質的な疑問はとりあえず横に置いておいて。

XMLRPCを使ったプロトタイプをPythonで作る場合、Python付属のSimpleXMLRPCServerxmlrpclibを使うのが一番簡単な方法ではないかと思います。Pythonのドキュメントにもこれらのライブラリを使ったXMLRPCサーバーおよびクライアントの解説があります。

ドキュメントに書かれている内容に従ってサーバーを実装すると、HTTPバージョンとして1.0が使われます。もちろん、普通に動作するのですが、RPCの度にTCP (HTTP) 接続を確立するので効率が気になります。短時間に大量のRPCを発行する場合は特にそうです。HTTPバージョン1.1では一度確立したHTTP接続で何度でもコマンドのやりとりが出きるので、できればHTTPバージョン1.1を使いたいと思っていたのですが、軽く検索してみてもなかなかこれといった情報が見つからず、かといって自前でHTTPバージョン1.1対応するのも面倒で放置していました。

先日Pythonのドキュメントを見ていたら、BaseHTTPRequestHandlerprotocol_versionという変数があり、これをHTTP/1.1に設定することでBaseHTTPRequestHandlerを使っているSimpleHTTPServerなどがHTTPバージョン1.1対応になるという記述を公式ドキュメントに発見しました。対象ライブラリの説明だけでなく、きちんと親クラスの説明も読まないとダメですね。SimpleXMLRPCServerもPRCリクエストの処理にBaseHTTPRequestServer由来のSimpleXMLRPCRequestHandlerを使っているのでなんとかなりそうです。


from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler

class HTTP11SimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
    protocol_version = 'HTTP/1.1'

class RPCMethods(object):
    def add(self, a, b):
        return a + b

server = SimpleXMLRPCServer(('', 8000), requestHandler=
HTTP11SimpleXMLRPCRequestHandler)

server.register_instance(RPCMethods())
server.serve_forever()


これでめでたくXMLRPCサーバーがHTTPバージョン1.1で応答するようになりました。xmlrpclibを用いたクライアントの方は、どうやら標準でHTTPバージョン1.1を使うらしく、サーバー側を対応するだけで永続接続が使えるようになります。

検索してでてこないということは、誰も使っていない (あるいは当たり前すぎて質問もされない) ということかもしれませんが、将来の自分のためのメモとして残しておきます。


2015年1月13日火曜日

Force10にOpen Network Linuxをインストール (その2)

Open Network Linux (ONL) で配布されている公式バイナリは、現在のところパケット転送機能を提供していません。これは2014年5月に開催されたOpen Compute Engineering WorkshopでBig Switch NetworksのRob Sherwood氏の資料にも明記してあります (14ページ)。実際、ONLのバイナリをForce10にインストールしただけの状態では管理用のイーサネットポートのみしか見えていなかったのは前回の記事に書いた通りです。

ハードウェアレベルでパケット転送をするためのルール設定にはASICとの通信が必要になります。通常、ASICへのアクセス情報を入手するためにはNDAの締結が必要になるため、ハードウェアアクセス部分を切り離してオープンソース化されているようです。

Rob Sherwood氏の資料によれば、以下の3種類のパケット転送モジュールが (2014年5月時点での) 近日中にリリースされると書かれています。

  1. Indigo2ベースのオープンフローエージェント
  2. OF-DPAベースの転送エージェント
  3. おもちゃの転送エージェントOpen Route Cache (ORC)
このうち、ORCは最新のONL配布物に含まれており、基本的な機能しか使えないものの、ONL箱をルータとして使うことができます。OF-DPAに関しては、githubで資料と参照コードが公開されているものの、現時点では簡単に利用できるものではなさそうです。

ORC自体のソースコードは公開されているのですが、Force10が採用しているBroadcomのASICにアクセスするためのライブラリはバイナリ状態でのみ提供されています。ORCはそのライブラリを動的にリンクし、ASICの操作を実現しています。

以下の手順でORCを起動します。

root@onl-powerpc:~# orc --daemon

ifconfig (もしくは ip link) コマンドでインターフェースを確認してみると、orcXXという名前で52個のインターフェースが作成されていることが確認できます。インターフェースにアドレスを設定すれば、他の機器と通信できます。また、ip routeコマンドなどで経路を設定すればパケット転送もできます。ハードウェア転送されていることを確認するには、tcpdumpコマンドでインターフェースをモニターしてみるとよいでしょう。Linuxカーネルにパケットが上がってこないため、tcpdumpでは転送されたパケットを観測することができないはずです。

ORCはLayer 3のパケット転送を補助するもので、Layer 2の機能は利用できません。スイッチ機器では複数のポートをひとつのブリッジにまとめて利用することが多いと思いますが、ORCではブリッジ設定ができません。残念ながら実用的な処理をさせることは難しいでしょう。

ORCのソースコードを見てみると、どうやって経路制御プログラムとASICの間で情報をやりとりすればよいかがうっすらと見えてきます。ORCでは、カーネルの経路制御操作時に発行されるNETLINKメッセージを観測し、インターフェースのアドレス情報、転送に必要となるFIBの情報などを適宜ASICライブラリを経由してハードウェアに落とし込んでいます。ASICライブラリはORC専用のもので、汎用的なアクセスライブラリが提供されているわけではないため、同じASICライブラリを別の用途で再利用するのは困難だと思われます。やはり本格的なスイッチを作ろうと思ったら、専用のアクセスライブラリを自作する必要があるようです。

Open Network Linuxは、ダウンドードしてきてそのまま使うソフトウェアというよりは、スイッチベンダー、SDN、NFV開発ベンダーが独自の機能を持った高性能なパケット転送装置を自作するための部品として考えるのが良さそうです。運用部分に慣れ親しんだLinuxを使い、なおかつ必要に応じてハードウェアアクセラレーション機能を組み込める箱が必要な場合には魅力的な選択肢になりそうでうs。