uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

embulk-output-orcを作った

techplay.jp
togetter.com

6月に開催されたPresto meetupで登壇したときに夏休みに作るわ〜と言ってしまったので宣言通り作ってみた。

embulk-output-orc | RubyGems.org | your community gem host

現状、マルチスレッドでちゃんと動かないバグを回避するために一時的な実装を入れています。

https://github.com/yuokada/embulk-output-orc/blob/0.2.2/src/main/java/org/embulk/output/orc/OrcOutputPlugin.java
github.com

diff --git a/src/main/java/org/embulk/output/orc/OrcOutputPlugin.java b/src/main/java/org/embulk/output/orc/OrcOutputPlugin.java
index c0ef4d8..d9352d9 100644
--- a/src/main/java/org/embulk/output/orc/OrcOutputPlugin.java
+++ b/src/main/java/org/embulk/output/orc/OrcOutputPlugin.java
@@ -34,6 +34,7 @@ import org.embulk.util.aws.credentials.AwsCredentialsTask;
 import org.joda.time.DateTimeZone;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -265,6 +266,7 @@ public class OrcOutputPlugin
     {
         private final PageReader reader;
         private final Writer writer;
+        private final ArrayList<VectorizedRowBatch> rowBatches = new ArrayList<>();
 
         public OrcTransactionalPageOutput(PageReader reader, Writer writer, PluginTask task)
         {
@@ -288,11 +290,8 @@ public class OrcOutputPlugin
                 );
                 i++;
             }
-            try {
-                writer.addRowBatch(batch);
-            }
-            catch (IOException e) {
-                Throwables.propagate(e);
+            synchronized (this) {
+                rowBatches.add(batch);
             }
         }
 
@@ -300,6 +299,9 @@ public class OrcOutputPlugin
         public void finish()
         {
             try {
+                for (VectorizedRowBatch batch : rowBatches) {
+                    writer.addRowBatch(batch);
+                }
                 writer.close();
             }
             catch (IOException e) {

Javaをこれまで全く書いてこなかったのでマルチスレッド周りのバグ対応の知見がなくこんなパッチになっています。
そして、この実装を取り入れた副作用として大量のレコード(自分の環境だとデフォルトの設定にでだいたい2~300万レコード以上)を処理するときに、java.lang.OutOfMemoryError: GC Overhead limit exceeded が発生して処理が落ちます。

自分の用途だと今のところ検証目的で100万程度までの処理が多いのでこの実装で間に合っていますし、 v0.2.0の実装でシングルスレッドで使えばレコードが増えても問題なく動くので今のところこれに落ち着きました。

ただ、ちゃんとバグ修正はしたいなと思っているので年内にでもちゃんと時間取って対応して1千万レコードでも1億レコードでも問題なく処理できるようにしたいとなと考えてます。

orcのなかでスレッドセーフじゃないクラスがいくつかあり、#addRowBatchからこれらを呼んでいるからかなというステータスでその先は調査中です。

orcフォーマットを軽く試す分には問題なく使えるレベルにはなっているので是非使って見てください。

clocとlocを試してみた。

clocとは?

Perl製のコードの行数を計測するツールです。
GitHub - AlDanial/cloc: cloc counts blank lines, comment lines, and physical lines of source code in many programming languages.

locとは?

clocにインスパイアされた(?)Rust製のコードの行数を計測するツールです。
GitHub - cgag/loc: Count lines of code quickly.

clocのインストール

早速、clocを試してみましょう。 今回はbrweからインストールしてみます。

% brew install cloc

今回はdjangoリポジトリを使ってパフォーマンスなどを見ていきます。

% git clone https://github.com/django/django.git
Cloning into 'django'...
remote: Counting objects: 398103, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 398103 (delta 0), reused 0 (delta 0), pack-reused 398101
Receiving objects: 100% (398103/398103), 163.06 MiB | 2.83 MiB/s, done.
Resolving deltas: 100% (289580/289580), done.
Checking out files: 100% (5766/5766), done.

% cloc django
   4005 text files.
   3902 unique files.
   1149 files ignored.

github.com/AlDanial/cloc v 1.74  T=41.37 s (81.9 files/s, 15448.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Python                        1883          50083          48320         216702
PO File                       1136          70500           9752         213272
JavaScript                      45           2938           3450          13531
HTML                           234            521             19           4414
CSS                             30            656            115           2755
JSON                            43              3              0           1415
XML                             14              0              2            207
DOS Batch                        1             23              1            165
make                             2             30              7            134
INI                              1              7              0             59
Bourne Shell                     1              4              7             17
-------------------------------------------------------------------------------
SUM:                          3390         124765          61673         452671
-------------------------------------------------------------------------------
>>> elapsed time 41s

コマンドの完了までに41秒かかってしまいました。 3300ファイルもあるとなかなか時間がかかりますね。

locのインストール

次に、locを実行してみましょう。今回はwgetgithubから取得してきます。

% wget https://github.com/cgag/loc/releases/download/v0.3.4/loc-v0.3.4-x86_64-apple-darwin.tar.gz
% tar zxvf loc-v0.3.4-x86_64-apple-darwin.tar.gz

% ./loc django
--------------------------------------------------------------------------------
Language             Files        Lines        Blank      Comment         Code
--------------------------------------------------------------------------------
Python                2419       315559        50191        19358       246010
Plain Text             436       127392        34876            0        92516
JavaScript              45        19919         2938         3451        13530
HTML                   302         5029          523           19         4487
CSS                     31         3526          656          115         2755
JSON                    44         1427            3            0         1424
XML                     15          220            0            2          218
Batch                    1          189           23            1          165
Makefile                 2          171           30            7          134
INI                      1           66            7            0           59
reStructuredText         3           85           26            0           59
Autoconf                 1           17            0            0           17
Bourne Shell             1           28            4            8           16
ASP.NET                  2            2            0            0            2
--------------------------------------------------------------------------------
Total                 3303       473630        89277        22961       361392
--------------------------------------------------------------------------------

locの方は実行時間1秒程度で完了しました。

ただ、clocの結果とかなり違うのでどっちが正確な結果なのか判断するのは難しいです。。。
実績でclocの方の結果が正確だろうというのが自分の予想です。

正確性のclocと早さのlocといった認識で使い分けをするのが良いのかなというところです。

Link

GoでHTTPリクエストのdebug proxyを書いた

f:id:uokada:20170515000805j:plain

タイトル通り、前のエントリーの続きでデバッグ用のproxyサーバーを書いたのでそのときに引っ掛かったところをメモ書き。

httputil - The Go Programming Language

GoでProxyサーバーをたてるにはhttputilパッケージにあるNewSingleHostReverseProxy を呼んで ReverseProxy をこねくり回してたてるのが一般的なので今回のProxyサーバーもこれを使って実装していく。

Proxyの部分は上でOKで次にデバッグのところだが、シンプルに受け取ったリクエスト/レスポンスをプリントしてもいいのだけれど、httputilパッケージにはDumpRequestDumpResponseの2つの関数があるのでこれを使う。

dumps, err := httputil.DumpRequest(req, true)
or
dumps, err := httputil.DumpResponse(resp, true)

たったこれだけでリクエスト/レスポンスに含まれるヘッダーとボディのダンプしたバイト列を取得することが出来る。
そして、これら2つの関数を利用したhttp.RoundTripperを実装してやればProxyサーバーの出来上がりです。 実装はこんな感じになります。 この例ではhttp.RoundTripperの実装にはリクエストだけ、レスポンスだけを出力するためのフラグを持たせています。

func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
    if t.DebugRequest {
        dumps, err := httputil.DumpRequest(req, true)
        if err != nil {
            return nil, err
        }
        debugPrint(string(dumps))
    }

    resp, err = t.RoundTripper.RoundTrip(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    b = bytes.Replace(b, []byte("server"), []byte("schmerver"), -1)
    body := ioutil.NopCloser(bytes.NewReader(b))
    resp.ContentLength = int64(len(b))
    resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
    resp.Body = body
    if t.DebugResponse {
        _body, err := httputil.DumpResponse(resp, true)
        if err != nil {
            return nil, err
        }
        debugPrint(string(_body))
    }
    return resp, nil
}

ちなみに、RoundTripper のコメントに次のような一文がある。

// RoundTrip should not modify the request, except for
// consuming and closing the Request's Body.

see: http - The Go Programming Language

今回はX-Forward-For ヘッダーなりを付与する必要がなかったので忠告に従ってそのまま後ろにいるサーバーにリクエストを投げるように実装しました。

今回初めてhttp.RoundTripの低レベルなところを触ってhttp周りだけでもまだまだGoの知らないところあったということを再認識した。 そして、標準でここまで揃えてくれてるGoはいつもながら素敵ですねと。

yuokada/debugproxy: Proxy server for request debug

  1. go - Golang: how to read response body of ReverseProxy? - Stack Overflow
  2. Go net/httpパッケージの概要とHTTPクライアント実装例 - Qiita
  3. Go http.RoundTripper 実装ガイド - Qiita

既存のWebサーバーをGoで実装し直すためにやってること

とあるWebサーバーが機能不足でちょっと使いづらいため、Goで実装し直そうとしている。
とりあえず、エントリーポイントが分かってるのでそこにどんなリクエストが飛んでいるかをダンプするところから始めている。

それで書いたコードがこれ。
httputilパッケージにDumpXXXってメソッドが生えているのでそれを使ってリクエストのダンプするサーバーの簡単なサンプルがこちら。
httputil - The Go Programming Language

package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
)

func debugFunc(w http.ResponseWriter, r *http.Request, )  {
    dump, _ := httputil.DumpRequest(r, true)
    fmt.Fprintln(w, string(dump))
    return
}

func main(){
    mux := http.NewServeMux()
    mux.HandleFunc("/info", debugFunc)
    http.ListenAndServe(":8080", mux)
}

内部処理はIDE使ったデバッグで動作確認しようと思うがとりあえずの入り口としてはこんな感じかなと思います。

github.com

正規表現とgorilla/muxを使ってHandlerを定義すればすべてのリクエストにマッチさせることも出来る。

package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
    "github.com/gorilla/mux"
)

func debugFunc(w http.ResponseWriter, r *http.Request, )  {
    dump, _ := httputil.DumpRequest(r, true)
    fmt.Fprintln(w, string(dump))
    return
}

func main(){
    m  := mux.NewRouter()
    m.HandleFunc("/{ep:[a-z/0-9]+}", debugFunc)
    http.ListenAndServe(":8080", m)
}

ちなみに、curlでリクエストを投げた時のレスポンスがこんな感じ。

$ curl -X GET -d foo=bar localhost:8080/v1/debug 
GET /v1/debug HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 7
Content-Type: application/x-www-form-urlencoded
User-Agent: curl/7.51.0

foo=bar

これにiptablesと合わせてやればまぁこんな感じでデバッグを出来るというところです。

劇場版 ソードアート・オンライン を観てきた

少し前になるが劇場版 ソードアート・オンライン -オーディナル・スケール-を観てきた。 sao-movie.net

自分はTVシリーズを本放送では観てなかったが、 劇場版の予告を映画館で観てこれは面白そうだと思い 1st TVシリーズ25話を観てから劇場に足を運んだ。

舞台はAR、仮想現実世界にソードアート・オンラインのダンジョン、アインクラッドのBOSSモンスターが登場するのがキッカケ。

ソードアート・オンライン(SAO)はTVシリーズは2まである作品だが劇場版は1stだけ見ていれば登場人物をほぼカバー出来たし、 舞台もアインクラッドでの出来事を中心にストーリーが進んでいたので2nd観ていなくて話ついていけるかという不安は全くなかった。

1st TVシリーズはAmazon prime videoで見れるよ。自分はそれでイッキ見した。

www.amazon.co.jp

個人的な感想としては最後の駆け足で話をまとめましたって感を受けた。 映像にしたら思ったよりも長かったのか時間が足りなかったのか、ラストのパートをもう少しでいいからじっくりと作ってくれていれば100点の内容だった。 あと、エンディングのスタッフロールの映像もしっかり描かれていてしっかりこだわりが伝わってきた。 全体を通すとストーリーはよくある系の話だけど王道抑える感じで自分は好きだし80点は超えるいい作品だった。 なので、2回め見に行った。

ラストシーンに登場する新たな冒険の舞台ラース。 名前だけ出てきただけでそれ以上は全く説明が無かったけどどういった話が展開されるのか今からワクワクですね。 (小説読んだらこの辺の詳細分かる!?) 3期なのかOVAで帰って来るのか分からないが期待して待っていようと思う。

最後に補足で、SAO、SAO劇場版の考察はこちらのre:buildfm のエピソードが詳しいのでこちらを聞くのがオススメ Rebuild: Aftershow 176: Xenoblade Vacation (naoya) rebuild.fm

naoyaさんによるSAOシリーズの各シーズンのテーマについての解説や劇場版の舞台であるAR世界とVR世界の違いなどの考察があってこれ聞いて方観ても面白い。

FlaskアプリにLDAP認証を組み込みたい

Flask製のアプリケーションにLDAP認証を組み込めないかと色々画策している。

Index of Packages Matching ‘flask’ : Python Package Index

それで、PyPIを探してみると何個かライブラリがあるのでどれを使っていいのか迷っている。
自分の中で同じことが出来るライブラリがある際の選定の基準として次のものがある。

  1. メンテナンスがされていること

    何年も前に開発が止まったライブラリを新規で採用するのはPythonなりFlaskで仕様変更があった際に対応できないのは辛い。
    選択肢がない場合はメンテナンスが止まっていても採用することがあるけど自前で拡張するのはある程度のコストが必要なので限られた時間のなかで

  2. 拡張性があること

    社内の事情で拡張する必要がある場合に比較的簡単に対応出来るような拡張の余地が残されていることも大事なポイント

  3. 導入のためのドキュメントが充実していること

    ドキュメントが充実している方が手軽に導入したい場合などはプラスポイント。
    ただし、コード量が少ない場合やよく知ってる言語ならドキュメントが少なくてもなんとかなるのでここは上の2つに比べたらそれほど重視するポイントではない。

今回はこの3つの観点からLDAP認証を実装するのに使うライブラリをいくつか洗い出してみる。
あとは、ライセンスも重要だけど今回は置いておく。

いくつか見比べた結果、Flask-Loginを使ってみることにした。
理由としては、Flask-Loginが拡張性に優れていること、PyPIの最終リリースが1年以内でメンテナンスが継続されていることなど上の条件を全て満たしているので今回はこれをチョイスした。

さて、これからGWあたりには、 Flask-Login を使ったPull-Requestを出せるように実装を頑張ろうと思う。

プロのための Linuxシステム・ネットワーク管理技術 (Software Design plus)

プロのための Linuxシステム・ネットワーク管理技術 (Software Design plus)

rpm作成のためのfpm入門

仕事でrpmパッケージを作る必要があったので久しぶりにこの辺を真面目に調べてます。

以前調べたときに、fpm使って楽できるって話をちらっと聞いたので今回のエントリーではfpmでhadoopのtar.gzからrpmを作ってみることにします。

github.com
Home · jordansissel/fpm Wiki

fpmのインストール

今回はHadooprpmを作るのがゴールなのでfpmを使うための最小限ぐらいのパッケージをインストールします。

$ sudo yum install ruby-devel gcc make rpm-build
$ sudo gem install fpm --no-ri --no-rdoc

インストールに苦労するものはないと思います。

Hadoopのソース取得

とりあえず、今回はここから取得してきましょう。
Index of /dist/hadoop/core/hadoop-2.7.3

$ wget http://www.apache.org/dist/hadoop/core/hadoop-2.7.3/hadoop-2.7.3.tar.gz
--2016-11-22 01:03:38--  http://www.apache.org/dist/hadoop/core/hadoop-2.7.3/hadoop-2.7.3.tar.gz
www.apache.org (www.apache.org) をDNSに問いあわせています... 2a01:4f8:130:2192::2, 88.198.26.2, 140.211.11.105
www.apache.org (www.apache.org)|2a01:4f8:130:2192::2|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 214092195 (204M) [application/x-gzip]
`hadoop-2.7.3.tar.gz' に保存中

100%[=========================================================================================================================>] 214,092,195 2.87MB/s 時間 74s

2016-11-22 01:04:53 (2.76 MB/s) - `hadoop-2.7.3.tar.gz' へ保存完了 [214092195/214092195]

fpmでrpm

今回のエントリーで使ってるバージョンは1.6.3です。

$ fpm -v
1.6.3

まずは、一番簡単な方法でrpmを作ってみます。 -sと-tオプションを渡してコマンドを実行します。

$ fpm -s tar -t rpm hadoop-2.7.3.tar.gz
Created package {:path=>"hadoop-2-1.0-1.x86_64.rpm"}

パッケージ・バージョンが少し辺ですがこれだけでもrpmを作ることに成功しました。


バージョンが変なので-vオプションを渡してさらに-nオプションも渡してみます。

$ fpm -s tar -t rpm --prefix /opt/myapp -v 2.7.3 -n apache-hadoop hadoop-2.7.3.tar.gz
Created package {:path=>"apache-hadoop-2.7.3-1.x86_64.rpm"}

バージョンの後ろにまだ1という数字がありますがこれはバージョンとは直接関係ないものです。
ここを変更するには --iteration で変更することが可能です。


さらに、 --prefix オプションを渡すことでパッケージをインストールするデフォルトのディレクトリを指定することも可能です。

$ fpm -s tar -t rpm --prefix /opt/myapp -v 2.7.3 -n apache-hadoop \
hadoop-2.7.3.tar.gz

今回は一番最後に実行したprefixオプションをつけて出来上がったパッケージが意図したとおりにインストールされるか確認して終わりにします。

$ sudo rpm -Uvh apache-hadoop-2.7.3-1.x86_64.rpm
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:apache-hadoop-2.7.3-1            ################################# [100%]

$ sudo rpm -qa apache-hadoop
apache-hadoop-2.7.3-1.x86_64

$ sudo rpm -ql apache-hadoop | head
/opt/myapp/hadoop-2.7.3/LICENSE.txt
/opt/myapp/hadoop-2.7.3/NOTICE.txt
/opt/myapp/hadoop-2.7.3/README.txt
/opt/myapp/hadoop-2.7.3/bin/container-executor
/opt/myapp/hadoop-2.7.3/bin/hadoop
/opt/myapp/hadoop-2.7.3/bin/hadoop.cmd

ちゃんと、/opt/myapp 以下に配置されてますね。
fpmを使うことでspecファイルを書かなくてもrpmが作れる方法の紹介でした。

追記

インストールしなくてもrpmコマンドだけで確認出来るみたいですね。

$ rpm -qpi apache-hadoop-2.7.3-100.noarch.rpm 
$ rpm -qpl apache-hadoop-2.7.3-100.noarch.rpm  |head

参考リンク