railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。 というチームでしたがISUCON5予選ダメでした #isucon

一昨年去年と出てて、今年が3回目のISUCONでした。 去年までは学生枠だったのですが、今年から社会人になってしまったので一般枠になってしまいました。

今年も、去年と同じ id:cnosuke さんと id:k0kubun さんと出ました。

競技中についてはk0kubunさんが大体全部書いてくれました → http://k0kubun.hatenablog.com/entry/2015/09/27/225243

上の記事のとおりですが、k0kubunさんががアプリケーション周りを全体的に、cnosukeさんがnginxとかredis周りとか、自分がMySQL周りを、なんとなく3人で分担してました。

したこと

ひたすらボトルネックを探してそこを潰す、潰したらまた探して潰す、、、という感じの流れでした。 k0kubunさんの記事に書いてあるようにジワジワとスコアが上がっていくのは見ていて楽しかったです。

New Relicとrack-lineprofがひたすら便利でした。 あと、cnosukeさんが書いたnginxのログからアクセス頻度見るツールとか使ってました。

自分はMySQL周りを見ていたので、slow queryを吐き出させてそこから頻出クエリと時間がかかっているクエリを探して、インデックス貼ったりクエリ改善できるかを模索したりしてました。 slow queryを吐き出す設定が何故かファイルに書いても動かなかったので、こんな感じで直接打ってました(適当)。

mysql> SET GLOBAL slow_query_log = 1; # オフにするときは0にする
mysql> SET GLOBAL slow_query_log_file = /var/log/mysql/slow.log;
mysql> SET GLOBAL long_query_time = 0;

以前は適当に手当たり次第「これ直りそうじゃない?」みたいなものを見つけて改善しようとしてましたが、 それだと当たり前ですが効果が全然出ないことが多いので、「推測するな、計測せよ」がメチャメチャ大切だと思いました。


結局最終的にスコア11045で終わりましたが、終了直前にベンチ回したのでもしかしたら若干違うかもです。

足切りラインが14000くらいだったので、もうちょっとで本戦にいけるかもしれなかったので悔しいです(ヽ´ω`)

今年は残念でしたが、また来年開催されましたらぜひ参加したいです!!!

JavaのライブラリのテストをJRuby+minitestで書く

お前は何と戦っているんだ感が半端ないですが、タイトルの通りJavaで書いたライブラリのテストをJRuby+minitestで書いてみました。

テストを書く対象のライブラリは、よくある(?)行列演算を行うやつです。 例としてMyMatrixLib.jar (net.rkmathi.lib.MyMatrixLib)という名前にしてみます。

JRuby, minitestのインストール

rbenvを使っているので、それ経由で入れました。

$ rbenv install -l | grep jruby
...
jruby-1.7.16
jruby-1.7.16.1
jruby-9.0.0.0-dev
jruby-9000-dev
...

jruby-1.7.16.1にしました。

$ rbenv install jruby-1.7.16.1

テスト書くディレクトリに移動して、rbenv localjrubyを使うようにします。

$ cd /path/to/test_directory
$ rbenv local jruby-1.7.16.1
$ rbenv rehash
$ jruby --version
jruby 1.7.16.1 (1.9.3p392) 2014-10-28 4e93f31 on Java HotSpot(TM) 64-Bit Server VM 1.8.0_25-b17 +jit [darwin-x86_64]

minitestのインストールをします。

$ jruby -S gem install minitest

ライブラリのJARを持ってくる

$ cd /path/to/test_directory
$ cp /path/to/MyMatrixLib.jar .

JRuby+minitestでテストを書く

とりあえず、get()とplus()とminus()のテストを書いてみるとこうなります。

require 'minitest'
require 'minitest/autorun'

require 'java'
require_relative './MyMatrixLib.jar'

import 'net.rkmathi.lib.MyMatrixLib'

class TestMyMatrixLib < Minitest::Test
  def setup
    @d1 = rand(1000.0)
    @d2 = rand(1000.0)
    @m1 = MyMatrixLib.new(3, 2, @d1)  # 3x2ですべての要素が@d1の行列を生成
    @m2 = MyMatrixLib.new(3, 2, @d2)  # 3x2ですべての要素が@d2の行列を生成
  end

  # get(r, c) => double: 行列のr行c列目の要素を取得
  def test_get
    each_assert_get @m1, @d1
    each_assert_get @m2, @d2
  end

  # plus(matrix) => MyMatrix: 行列のそれぞれの要素を足した行列を生成
  def test_plus
    d_res = @d1 + @d2
    m_res = @m1.plus(@m2)  # @m1と@m2のそれぞれの要素を足した結果の行列
    each_assert_get m_res, d_res
  end

  # minus(matrix) => MyMatrix: 行列のそれぞれの要素を引いた行列を生成
  def test_minus
    d_res = @d1 - @d2
    m_res = @m1.minus(@m2)  # @m1と@m2のそれぞれの要素を引いた結果の行列
    each_assert_get m_res, d_res
  end

  private

  # 行列の各要素をそれぞれgetするのが面倒なのでメソッド化
  def each_assert_get(m, d)
    for r in 0...m.get_row_dimension do
      for c in 0...m.get_column_dimension do
        assert_equal m.get(r, c), d
      end
    end
  end

  ...

end

と、こんな感じで書くことができます。

ちなみにJUnitだと...

public class MyMatrixLibTest {
    @Before
    public void setUp() throws Exception {
        MyMatrixLib d1 = Math.random() * 1000.0;
        MyMatrixLib d2 = Math.random() * 1000.0;
        MyMatrixLib m1 = new MyMatrixLib(3, 2, d1);
        MyMatrixLib m2 = new MyMatrixLib(3, 2, d2);
    }

    @Test
    public void testGet() {
        eachAssertGet(m1, d1);
        eachAssertGet(m2, d2);
    }

    @Test
    public void testPlus() {
        double d_res = d1 + d2;
        MyMatrixLib m_res = m1.plus(m2);
        eachAssertGet(m_res, d_res);
    }

    @Test
    public void testMinus() {
        double d_res = d1 - d2;
        MyMatrixLib m_res = m1.minus(m2);
        eachAssertGet(m_res, d_res);
    }

    private void eachAssertGet(MyLibMatrix m, double d) {
        for (int row = 0; row < m.getRowDimension(); row++) {
            for (int column = 0; column < m.getRowDimension(); column ++) {
                assertEquals(m.get(row, column), d);
            }
        }
    }
}

簡単な演算のテスト部分しか比較しませんでしたが、書いてて楽には思えました。

ですが、JRubyを書いていると文法とか変数の型をミスった時に吐かれる例外がよくわからないことになることもあるので、わざわざJRubyでテスト書くのは微妙かも...

VPS上にSoftEther VPN ServerをセットアップしてOS Xからつなぐ

HDD容量を少なくすればめちゃ安いVPS( http://www.idcf.jp/cloud/ )を教えてもらったので、ここにSoftEtherVPN Serverをセットアップしてみました。

ちなみに、仮想マシンを作成するときに「テンプレート」からだとHDD容量が最低15GBなので540円/月ですが、「ISO」から作るようにすると最低1GBから選べるようになります。例えば5GBにすれば324円/月で済みます。

ポートの設定

VPSのコントロールパネル側でファイアウォールの設定があるので、

のポートを開けておきます。

SoftEther VPNのダウンロード

SoftEther ダウンロードセンターから、SoftEther VPN (Freeware)SoftEther VPN ServerLinuxIntel x64 / AMD64 (64bit)を選んで、最新ビルドのリンクのアドレスをメモっておきます。

あとで、OSをインストールしたらこのリンクから引っ張ってきます。

OSのインストールとファイルの配置

適当に、Ubuntu Server 14.04 amd64をインストールしておきました。

### 必要なパッケージのインストール
~$ sudo aptitude update
~$ sudo aptitude upgrade
~$ sudo aptitude install build-essential sysv-rc-conf

### さっきのリンクからダウンロード
~$ wget http://jp.softether-download.com/files/XXX/softether-vpnserver-XXX-linux-x64-64bit.tar.gz
~$ tar xvf softether-vpnserver-XXX-linux-x64-64bit.tar.gz
~$ cd vpnserver

### 確認したら、1でAcceptしてmake
~/vpnserver$ make
1
1
1

### vpnserverのパーミッションを変更
~/vpnserver$ chmod 600 *
~/vpnserver$ chmod 700 vpncmd vpnserver
~/vpnserver$ cd ../
~$ sudo mv vpnserver /usr/local/
~$ sudo chown -R root:root /usr/local/vpnserver
~$ cd /usr/local/vpnserver

### 動作確認
/usr/local/vpnserver$ sudo ./vpncmd
3
check
exit

/usr/local/vpnserver$ cd

### initスクリプトを書いて、パーミッションを755にしておく
~$ sudo chmod 755 /etc/init.d/vpnserver
~$ sudo cat /etc/init.d/vpnserver
#!/bin/sh
# description: SoftEther VPN Server
DAEMON=/usr/local/vpnserver/vpnserver
LOCK=/var/lock/vpnserver
test -x $DAEMON || exit 0
case "$1" in
start)
$DAEMON start
touch $LOCK
;;
stop)
$DAEMON stop
rm $LOCK
;;
restart)
$DAEMON stop
sleep 3
$DAEMON start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit 0

### sysv-rc-confでvpnserverの2,3,4,5にチェック
~$ sudo sysv-rc-conf
...

### 再起動
~$ sudo reboot

SoftEther VPN Serverのセットアップ

~$ cd /usr/local/vpnserver

/usr/local/vpnserver$ sudo ./vpncmd
1
localhost
(Enter)

### VPN Serverのパスワードを設定
VPN Server> ServerPasswordSet
(パスワードを設定)

### 仮想ハブを作成 ここではVPNHUBという名前にしてある
VPN Server> HubCreate
Name of Virtual Hub to be created: VPNHUB

### VPNHUBに切り替え
VPN Server> Hub VPNHUB

### SecureNatを有効に
VPN Server/VPNHUB> SecureNatEnable

### ユーザを作成 ここではvpnという名前にしてある
VPN Server/VPNHUB> UserCreate
vpn
(Enter)
(Enter)
(Enter)

### 上で作ったvpnユーザのパスワードを設定
VPN Server/VPNHUB> UserPasswordSet
vpn
(VPN User Password)
(VPN User Password)

### IPSecを有効に
VPN Server/VPNHUB> IPSecEnable
Enable L2TP over IPsec Server Function (yes / no): yes
Enable Raw L2TP Server Function (yes / no): no
Enable EtherIP / L2TPv3 over IPsec Server Function (yes / no): no
Pre Shared Key for IPsec (Recommended: 9 letters at maximum): (IPsec Secret Key)
Default Virtual HUB in a case of omitting the HUB on the Username: VPNHUB

VPN Server/VPNHUB> exit

$ sudo service vpnserver restart

OS X(クライアント)側の設定

システム環境設定ネットワーク→左下の+マークで追加

f:id:rkmathii:20141128000937p:plain

↑画像のサーバアドレスアカウント名を埋めたら、下にある認証設定…をクリック

f:id:rkmathii:20141128001148p:plain

ユーザ認証 パスワードに(VPN User Password)を、コンピュータ認証 共有シークレットに(IPsec Key)を入力

これで接続ができるように(┐「ε:)

GitLabでMaven Repositoryを作ってGradleから使う

研究室でJavaのプログラムを書いているのですが、ライブラリを研究室内で共有するときにMaven Repositoryを立てたらいいのではないかと思ったので、GitLab上で使うようにしました。

想定するプログラム

  • ライブラリ(MyLib)
  • MyLibを使用するプログラム(MyProgram)

MyLibのbuild.gradle

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'maven'

def defaultEncoding = 'UTF-8'
[compileJava, compileTestJava]*.options*.encoding = defaultEncoding
sourceCompatibility = 1.7
targetCompatibility = 1.7

group = 'net.rkmathi'
archivesBaseName = 'mylib'
version = '1.0.0'

repositories {
    maven { url System.getenv('HOME') + '/.m2/repository' }
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.11'
}

uploadArchives.repositories.mavenDeployer {
    repository(url: "file:${projectDir}/maven")
}

これでgradle uploadArchivesを実行すると、mavenディレクトリ以下に出力されます。

  • uploadArchivesで出力したら、gitリポジトリにそのディレクトリも追加して、GitLabへプッシュします。

MyProgramのbuild.gradle

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'maven'

def defaultEncoding = 'UTF-8'
[compileJava, compileTestJava]*.options*.encoding = defaultEncoding
sourceCompatibility = 1.7
targetCompatibility = 1.7

group = 'net.rkmathi'
archivesBaseName = 'myprogram'
version = '1.0.0'

repositories {
    maven { url System.getenv('HOME') + '/.m2/repository' }
    maven { url 'http://<<GitLab Address>>/<<User Name>>/mylib/raw/master/maven' }
    mavenCentral()
}

dependencies {
    compile 'net.rkmathi:mylib:1.0.0'
    testCompile 'junit:junit:4.11'
}

uploadArchives.repositories.mavenDeployer {
    repository(url: "file:${projectDir}/maven")
}

これで、MyProgramからMyLibを使うことができました。

  • http://<<GitLabのアドレス>>/<<ユーザ名>>/<<プロジェクト名>>/raw/mastermaven repositoriesの場所に指定することによって、GitLab上にあるライブラリを取得できるようになります。

ワーイ

ISUCON4本戦に参加したけどダメでした

ISUCON4の本戦に@cnosukeさんと@k0kubunさんの3人で、「railsへの執着はもはや煩悩」というチームで出てきました。

cnosukeさんのブログ( http://cnosuke.hatenablog.com/entry/2014/11/11/141321 )やk0kubunさんのブログ( http://k0kubun.hatenablog.com/entry/2014/11/09/003455 )に状況とか詳しく書いてあるのであんまり書くことないですが、とりあえず最終結果は6,000点も行かずにダメダメな感じでした(ヽ´ω`)

戦略

f:id:rkmathii:20141109151912p:plain

動画広告の配信だし、多分ベンチマークから攻撃のようなアクセスをされるだろうと思い、3台与えられたサーバ全部に対してアクセスさせるようにしました。

ただし、各サーバともメモリが1GBしか積んでいなかったためスワップしまくりだし、OOM Killerにunicornだのvarnishだのを殺されて辛い感じでした。

反省とか

ISUCONの敵であるbenchmarkerの挙動をもっとちゃんと把握しておくべきでした。

benchmarkerからのアクセスログを覗いていたら、"Galaxy Nexus"とかいろんな端末のUAがあって「なんかbenchmarkerめちゃ凝ってるぽいなー」とは思ったのですが、まさかブラウザの挙動を模してキャッシュ機能とかまで備えているとは考えられませんでした。

何か問題があってそれに対して高速化を図るのに、その問題をきちんと把握せずに取り組むのは今更ですが良くなかったです(ヽ´ω`)

(でも競技時間が8時間しかないので焦ってしまう…)

おわり

今年で学生は終わりなので最後の学生枠だったのですが、本戦まで行けて(一応)最終結果をFailせずに終われたのはよかったです。

来年も出場したいので、社会人枠で戦えるようにちゃんと勉強していきます。

ISUCONは競技中はずっと緊張感があり、とても楽しかったです。

運営をしてくださった皆様に感謝しております。本当にありがとうございました。