qunit-tapとproveを使ってJSの単体テストのCIをする方法

「phantomjs + qunit-tapでCLI単体テスト」をするという素晴らしい方法をt-wadaさんが書かれてました。これを少し応用するだけでJavaScript単体テストのCIすることができます。
http://www.slideshare.net/t_wada/wandering-about-javascript-testing

FTレベルの自動化はSeleniumでいいかなと思ってたんですが、単体テストレベルのCIの自動化には今まであまり良いソリューションがありませんでした。 

ブラウザのシミュレータ系はいつもシミュレートする側のバグなのかという制約を意識しながら進めることになり、今まで何回か導入しようとして失敗していました。

しかし、qunit-tap + phantomjs + (prove + TAP::Harness::JUnit) + Jenkins を使えば、この問題が解決できます。phantomjsを使う方法であれば実際のブラウザに限りになく近いわけで、これはとても良さそうです。

JavaScriptのUTのCIまでできれば、チームでも使えるようにになりそうです。そして、これは以前PerlのプロジェクトでJenkins(Hudson)を使う方法を説明しましたが、その方法で実現できます。
http://dann.g.hatena.ne.jp/dann/20100404/p1

qunit-tapというt-wadaさんのプロダクトは、TAPでの出力に対応しています。ですから、proveとTAP::Harness::JUnitを組み合わせれば、JUnit形式の結果の出力ファイルを生成することができます。

要するに、以下のようにJenkins側でproveを実行して、Jenkinsに出力されたJUnit形式のXMLファイルを食わせれば、CIを実現することができます。

prove --harness TAP::Harness::JUnit ./phantomjs_test.sh 

phantomjs_test.shのサンプルについては、t-wadaさんのレポジトリに例があるので、少し書き換えて使えばいいですね。
https://github.com/twada/qunit-tap/blob/master/sample/js/phantomjs_test.sh

これでFTレベルだけでなく、javascriptのUTレベルでもCIができるようになり、チーム開発でも使えるようになりますね。t-wada++

トランザクション数の多いDBサーバーのディスク構成について

IOPSが高い値を維持でき、ある程度コストを抑えたDBサーバーのディスク構成を考えてみる。RAIDカードを全てのスレーブに置くというのは価格がかかりすぎること、SSDが安くかえるようになったことを考えると、以下のような構成が、そこそこの価格でIOPSを稼ぐための構成だと考える。

DBサーバーの構成

  • マスターDBのサーバー
    • DBミドルからのwriteがすぐに終わるように、RAID+BBWCを使いwriteのIOPSを高める
      • InnoDBログファイルやREDOログなどの同期書き込みのコストは極めて高いので、BBWCによりバッファリングすることで大幅にディスク負荷を減らすことができ、特にマスターDBではRAIDカードは使いたい
    • データファイルはランダムライト、ログファイルはシーケンシャルライトになることから、きわめてトランザクション量が多い場合には、データファイルとログファイルを分離し、I/Oパターンを分離する。ただし、運用の容易さを考えれば、データファイルとログファイルはディスクは分けずに、RAID1+0だけで管理してもよい。
      • データファイルをRAID1+0にして、SSDに配備
      • ログファイルをRAID1にして、HDDに配備
  • スレーブDBサーバー
    • スレーブは台数が多く必要になるので、SSDだけの構成にしてRAIDカード代を安く押さえる
    • ただし、スレーブについても、全てのサーバーにRAIDカードを配置できるだけコストをかけられる場合には、SSDのRAID1+0構成を組めばよい。

マスターについては、(RAID+BBWC) + SSD、(RAID+BBWC)+HDDで、ライトのIOPSと運用体制によっていずれかを選ぶ。また、トランザクション数が極端に大きくなく、定常的にトランザクション数が多いという状態でなければ、RAID+BBWCとHDDでの運用でよい。

SSDの運用について

SSDを使う場合には、HDDと違う運用の検討が必要になってくるので、その点注意が必要である。注意点だけでなく、それを考慮した運用が行える体制があるかが、導入上大きなポイントになる。

  • TrimのサポートがRHEL6からということを考えると、SSDで運用する場合には、ディスクの使用量が増えてきたときのことを事前に検討する必要ではある。
  • 性能劣化を前提に性能劣化の範囲を実ディスクで確かめた上でSSDを配備する必要があるという点で、HDDとは違う運用が必要になるので、その点についてはSSDの耐久テスト+性能テストをした上で、ディスク使用率が上がってきたときの劣化時の性能を確認しておく必要がある

FusionIO/tachIOn の使用について

  • FusionIO, tachIOnが数万のIOPSが出ることを考えると、書き込みするデータ量が多くなければ、オーダーとして書き込み回数が10倍以上多くてもよいわけで、DBサーバーを最大1/10程度にできる可能性はあり、それを考えると十分に検討の余地はありそう。
  • トランザクション数が多く、導入しない場合に、台数が増えて、運用コストが初期のイニシャルコストを超える場合には、導入を検討する価値がある。ただし、本当の大規模サービスでなければ、それをペイできる環境ではないとは思う。

IOPS視点のディスクのプラニングについて

以下の2点を行い、IOPSの増加に対してどのようなプランをたてるかを検討することが重要。I/Oサブシステムの性能を意識しておくことで、どのくらいでI/Oが限界にくるかがわかるようになるからである。

  • I/OサブシステムにおけるI/Oの性能を把握しておくこと(bonnie++やIOMeter)
  • iostatのw/sとr/sの傾向を見て、IOPSの増加傾向を把握すること
    • 特に、データファイルとログファイルのディスクが分離されていると、IOパターン毎のIO回数が把握しやすくなり、どのI/Oパターンに対して強化をしていくかが検討しやすくなる。

ディスクへのIO(特に回数)はネックになりやすく、これはMySQLOracleといったDBだけでなく、他のミドル全般にいえる。従って、ディスクの性能状況については特に注意深くモニタリングし、問題の発生を事前に予見できるようにしておく必要がある。

封筒裏の計算の大切さ

性能やスケーラビリティが求められるシステムでは、どこにどれくらいの性能オーダーが必要なのかを検討するのは、アーキテクチャを検討する上でとても重要になる。封筒裏の計算の段階で大体のオーダーがわかれば、必要になるSWのアーキテクチャアルゴリズム、ハードがある程度はわかり、そのレベルで破綻していないことは事前に検証ができる。

それらの大体のオーダーについて、以下の3つのblogではまとめている。開発/インフラ運用の両方で、どこでどの程度の性能オーダーになっているのかを理解できていると、破綻のしない開発/運用が行えると思う。

ハードも年々進化していくわけで、どのようなアーキテクチャでどのようなオーダーで、どの部分が動作するのかについては、意識してウォッチしていきたいと思う今日この頃。

L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns
Mutex lock/unlock 100 ns
Main memory reference 100 ns
Compress 1K bytes with Zippy 10,000 ns
Send 2K bytes over 1 Gbps network 20,000 ns
Read 1 MB sequentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Disk seek 10,000,000 ns
Read 1 MB sequentially from network 10,000,000 ns
Read 1 MB sequentially from disk 30,000,000 ns
Send packet CA->Netherlands->CA 150,000,000 ns 

Kindle使いにおすすめしたい3つのShortcut

あまり操作性がよいとはいえないKindleですが、3つのショートカットを知っているだけでちょっと便利に使えます。

  • Alt+一番上の行の英字
    • 数字入力
    • SYMおさなくても、数字が入力できます。ページ移動のときに便利です。
  • ブックマーク追加
    • ALT + B
  • 画面のリフレッシュ
    • ALT + G
    • 画面が黒くなってきたときに、時々リフレッシュしてやると白くなります

# 1万円程度でKindleが買えるなんて本当にいい時代になったものだなぁと思う今日この頃です。

ChromeのVimiumでスムーズにスクロールするようにさせてみた

Vimperatorのsmoozieをvimiumに移植しました。vimiumのscroll step sizeの設定は、400くらいにすると結構快適に使えます。
https://github.com/dann/vimium

これでvimumでもスクロールがスムーズになり快適に使えるようになりました。

はてなブックマークとInstantPaperを連携させてみた

LDR -> はてなブックマーク -> EverNote というワークフローで情報の蓄積をしているんですが、普段Kindleを使っているので、ゆっくり読みたい物についてはKindleで読むのもいいかもしれないと思うようになりました。

そこで、はてなブックマークに登録したエントリをInstantPaperに送るスクリプトを作ってみました。InstantPaperからはKindleにエントリを送信することができる機能があるので、これでKindleまでエントリを送ることができるようになります。

使い方

  • コード中のConfigのところに書いてある、はてブRSSとInstantPaperに送るはてブのtagを適宜編集
  • perl hatebu2instantpaper.pl

InstantPaperからKindleにおくるための設定

InstantPaperのページに詳細は書いてありますが、InstantPaperからKindleに配送するためには、事前に以下の設定をしておく必要があります。詳しくはInstantPaperのページで。

  • InstantPaperで、youraccount@free.kindle.comに送るように設定
  • Amazonで、Your Kindle approved e-mail listに、kindle.rtaad@instapaper.comを追加

コード

https://github.com/dann/hatebu2instantpaper

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Config::Pit;
use Carp ();
use LWP::UserAgent;
use URI::QueryParam;
use XML::RSS;
use Scalar::Util qw(reftype);
use List::MoreUtils qw(any);

# Config
my $HATEBU_API_ENDPINT = "http://b.hatena.ne.jp/dann/rss";
my $HATEBU_TAG         = "tokindle";

# Main
my $INSTANT_PAPER_API_ENDPOINT = "https://www.instapaper.com/api/add";
my $CONFIG;

main();
exit;

# Logic
sub main {
    init();
    my $entries = fetch_entriies_from_hatebu();
    post_entries_to_instantpaper($entries);
}

sub init {
    $CONFIG = pit_get(
        "hatebu2instant",
        require => {
            "instant_username" => "your username for instantpaper",
            "instant_password" => "your password for instantpaper",
        }
    );
    die 'pit_get failed.' if !%$CONFIG;
}

sub fetch_entriies_from_hatebu {
    say("--> Fetching entries from HatenaBookmark");
    my $ua                  = LWP::UserAgent->new;
    my $hatebu_api_endpoint = URI->new($HATEBU_API_ENDPINT);
    my $res                 = $ua->get($HATEBU_API_ENDPINT);

    my $rss     = _parse_rss( $res->content );
    return $rss->{'items'} || [];
}

sub _parse_rss {
    my $rss_text = shift;
    my $rss      = XML::RSS->new;
    eval { $rss->parse($rss_text) }
        or Carp::croak( "Parsing content failed: " . $@ );
    $rss;
}

sub post_entries_to_instantpaper {
    my $entries = shift;
    say("--> Posting HatenaBookmark entries to InstantPaper ... ");
    foreach my $entry (@$entries) {
        my $tags = $entry->{'dc'}->{'subject'} || [];
        $tags = [$tags] unless reftype $tags; 
        my $contain_tag = any { $_ eq $HATEBU_TAG } @{ $tags };
        post_entry_to_instanpaper($entry) if $contain_tag;
    }
}

sub say {
    my $text = shift;
    print "$text\n";
}

sub post_entry_to_instanpaper {
    my $entry        = shift;
    say("==> Posting '$entry->{title}'");
    my $ua           = LWP::UserAgent->new;
    my $endpoint_uri = URI->new($INSTANT_PAPER_API_ENDPOINT);
    $endpoint_uri->query_form_hash(
        username => $CONFIG->{instant_username},
        password => $CONFIG->{instant_password},
        url      => $entry->{link},
    );
    my $res = $ua->get($endpoint_uri);
    unless ($res->is_success) {
       print STDERR  $res->status_line, "\n";
    }
}

# ついでにPlaggerのPlugin化もしてみました
https://github.com/dann/p5-plagger-plugin-publish-instantpaper