Lens の実用例

ご無沙汰しています。最近ヒトカラがマイブームのあおいの(@aoiroaoino)です。
そういえば ScalaMatsuri 2016 のスポンサー LT でこんなこと言っていたのを思い出しました。

「弊社では Lens(Monocle) を test で使用しています。」

今回はこれの話をしようと思います。

続きを読む

0.7以降のmsgpack-javaを使って古いフォーマットでシリアライズする方法

こんにちは、chaltosです。

今回は、msgpack-javaのバージョンを0.6.Xから上げたいが0.6.Xと同じフォーマットでシリアライズしたい場合の対処についてです。

MessagePackとは何なのかといった部分は省略させていただきます。

続きを読む

JVMのバイトコードの実行時最適化について発表してきた

こんにちは、todeskingです。

ScalaMatsuri 2016将軍スポンサーが合同で開催した「Scala将軍達の後の祭り」 というイベントで、バイトコードの実行時最適化について発表してきました。

抽象化によってオーバヘッドが存在するコードを実行時にバイトコードレベルで最適化すれば、抽象化とパフォーマンスが両立出来てお得、という夢のある話です。

以下、補足など。

続きを読む

Redis Expire Performance

Scala書けますで入社した筈なのにnewrelicみながらDBのチューニングばかりしてる、ぽんこつです。

今回はRedisをどうやって叩くと早くなるのかを、主にexpireまわりで適当にベンチしたりした。

私のDBの経験値がMySQLに偏っているのであまり手出ししてこなかった弊社のRedis環境だが、パフォーマンス絡みの問題を聞いたり見かけるようになってきたので、Redisのパフォーマンスを引き出す方法について調査をした。

弊社のサーバアプリケーションは主にScalaで書かれているので、ScalaからRedisに繋いだときのベンチマークが中心になる。

JMH

JMHはマイクロベンチマークツールの一種。JVMJITによって、何度も同じ箇所を実行すると、徐々にコードを最適化して早く動くようになる、等々の要素を排除し、できるだけ正しいベンチマークをすることができるツール。

ScalaでJMHを使う場合はsbt-jmhを使うと良い。sbtのpluginとして使うことができる。今回はsbt-jmhを使っている。(ただしRedisの場合I/Oがからむので、JMH本来の正確さは期待できないと思われる)

BulkInsert

とりあえず30万件データを突っ込むのに掛かる時間を調べる。jedisのオーバヘッドが大きい可能性を考慮してまずは最速パターン(と思われる)を計測してからjedisの計測に入る。

shell

for i in `seq 1 10000`
do
    redis-cli set ${i}key ${i}value
done

適当に作ったが、あまりにも時間掛かり過ぎて計測不能だった。1コマンド毎にredis-cliを立ち上げるのは無謀だった模様。

shell (redis protocol)

一旦コマンドリストを生成して流し込んでもいいけど、今回はredis protocolを使ってみる。

http://redis-documentasion-japanese.readthedocs.org/ja/latest/topics/mass-insert.html

redis protocolをpipeで流せばいいらしい。該当ページのRubyスクリプトを参考に組んでみる。

def gen_redis_proto(*cmd)
  proto = ""
  proto << "*"+cmd.length.to_s+"\r\n"
  cmd.each{|arg|
    proto << "$"+arg.to_s.bytesize.to_s+"\r\n"
    proto << arg.to_s+"\r\n"
  }
  proto
end

count = ARGV.size > 0 ? ARGV[0].to_i : 300000

(1..count).each { |i|
  puts gen_redis_proto("SET","key%d" % i,"value%d" % i)
}
puts gen_redis_proto("FLUSHDB")

これで生成したredis protocolをredis-cliに渡す

ruby gen_proto.rb | time redis-cli --pipe

結果は1.303s。はやい。

jedis

Javaに対応したバインディングは幾つもあるが、今回は一番メジャーっぽいjedisを使う。社内で使ってるからという話もある。

@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
def jedis(): Unit = {
  val client = new Jedis()
  (1 to 300000).foreach { i =>
    client.set(s"key${i}", s"value${i}")
  }
  client.flushDB()
}

Warm upを3回、Iterationを5回やって平均を出す。

jmh:run -i 5 -wi 3 -f1 -t1 org.mvrck.BulkInsertTest

結果は4.274s。ちょっと遅過ぎる気がする

jedis multi

コマンドを毎回発行してるから遅いと思われるので、Transactionを使ってみる

https://github.com/xetorthio/jedis/wiki/AdvancedUsage

今回からannotationは省略して書く

def setMulti(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (1 to 300000).foreach { i =>
      t.set(s"key${i}", s"value${i}")
    }
  }
  client.flushDB()
}

def multi[T](jedis: Jedis)(f: Transaction => T): T = {
  val multi = jedis.multi()
  val res = f(multi)
  multi.exec()
  res
}

0.541s。アイエェ!?redis protocolより早いナンデ!?timeの時間計測に問題あるのかな…?まぁ早いならいいかな…(適当)

expire遅くないか

と本番環境のnewrelic見て思ったので調査する。

setWithExpire

まずはset時にexpireを設定する

def withExpire(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (1 to 300000).foreach { i =>
      t.set(s"key${i}", s"value${i}", "NX", "EX", 100)
    }
  }
  client.flushDB()
}

0.792s。割と遅くなる。

別でExpireする

挿入する物によっては1コマンドでexpire設定できないこともあるので、それを模してみる

def splitExpire(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (1 to 300000).foreach { i =>
      t.set(s"key${i}", s"value${i}")
      t.expire(s"key${i}", 100)
    }
  }
  client.flushDB()
}

1.031s。更に遅くなる。ので、setするときにexpireが設定できないリスト型などで速度が気になりそう。

expire time

expireを設定したkeyが実際に削除されるタイミングで負荷が上がるのでは、という仮説があったので調査する。1msで揮発するようにすればそれっぽいテストができそう。

def withLittleExpire(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (1 to 300000).foreach { i =>
      t.set(s"key${i}", s"value${i}", "NX", "PX", 1)
    }
  }
}

1.002s。単純にexpireを設定した場合が0.792sなので遅くなってる。瞬間的に揮発のタイミングが重なるとちょっとあぶないかもしれない。

read時にexpire

Redisの使用用途は主にcacheなので、使用頻度が低い奴は消えて欲しいけど高いやつは残って欲しい。良くある。そこでreadするときにexpireすればいいのでは!本当か?

read only

比較対象としてreadの速度を計測する。

def read(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (0 until 100).foreach { i => t.set(s"key${i}", s"value${i}") }
  }
  multi(client) { t =>
    (1 to 300000).foreach { i => t.get(s"key${i%100}") }
  }
  client.flushDB()
}

0.286s。流石危険な程早いと言われるRedis。

read with expire

expireを付けるとどうなるか。

def readWithExpire(): Unit = {
  val client = new Jedis()
  multi(client) { t =>
    (0 until 100).foreach { i => t.set(s"key${i}", s"value${i}", "NX", "EX", 100)}
  }
  multi(client) { t =>
    (1 to 300000).foreach { i =>
      val key = s"key${i % 100}"
      t.get(key)
      t.expire(key, 100)
    }
  }
  client.flushDB()
}

0.618s。倍ぐらい掛かるようになった。

倍ならまぁ、という気もするが、可能ならばRedisサーバ側の設定を変更し、maxmemory-policyのallkeys-lruなどで設定した方が良いと思われる。

まとめ

Redisのexpireすごく遅くないか、という疑惑があり色々比較してみたけど、大体倍ぐらいだったので、maxmemory-policyの検討をしつつ必要なら使えば良いのでは、という結論になった。あとtransactionは強力なのでできるだけ使うようにしたい。

弊社での実測の話をすると、getの度にexpireした結果として、expireの処理時間が結構長いことがnewrelicで判明した。このため、getと同時にttlコマンドで残り期間を確認して、伸ばす必要があるときだけ伸ばすようにした結果、expireが激減して全体として高速化した。ただしその場合transactionが使えなくなるので今回は計測しなかった。

今回テストに使ったコードは以下にある。

https://github.com/mvrck-inc/sandbox/tree/master/redis-performance

計測方法にあまり自信が無いので、問題がありそうなら教えて欲しい。

今回の環境

OS: Arch Linux
Redis: 3.0.7
JDK: OpenJDK 1.8.0_74
CPU: Core i5 4690T
Memory: 16GB

SSL更新

今回はssl更新の記事です。

インフラの方々は作業されたことがあると思いますが私は初挑戦です!

macで作業します。

バージョンを確認

[astel@astel-no-MacBook-Pro ] $ openssl version
OpenSSL 0.9.8zg 14 July 2015

秘密鍵作成のための乱数ファイルを生成します。

[astel@astel-no-MacBook-Pro ] $ dd if=/dev/urandom of=rand.dat bs=1k count=4
4+0 records in
4+0 records out
4096 bytes transferred in 0.000492 secs (8327615 bytes/sec)

[astel@astel-no-MacBook-Pro ] $ ls
rand.dat

作成した乱数ファイルから秘密鍵を生成します。

途中で秘密鍵のパスワードを入力します。

[astel@astel-no-MacBook-Pro ] $ openssl genrsa -rand rand.dat -des3 2048 > private_key.pem
4096 semi-random bytes loaded
Generating RSA private key, 2048 bit long modulus
..........+++
.......+++
e is 65537 (0x10001)
Enter pass phrase: (パスワード入力
Verifying - Enter pass phrase: (もういちど入力

[astel@astel-no-MacBook-Pro ] $ ls
private_key.pem rand.dat

CSRを作成します。

[astel@astel-no-MacBook-Pro ] $ openssl req -new -key private_key.pem -out csr.pem
Enter pass phrase for private_key.pem: (上記で入力した秘密鍵のパスワードを入力
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Shinjuku-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Maverick Inc.
Organizational Unit Name (eg, section) []:Development
Common Name (e.g. server FQDN or YOUR name) []:*.mvrck.co.jp
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

[astel@astel-no-MacBook-Pro ] $ ls
csr.pem     private_key.pem rand.dat

作成したCSR(csr.pem)を申請サイトへ貼り付けて申し込みます。

今回発行されたSSL証明書_.mvrck.co.jp.crtと名前を変えてサーバへ設置していきます。

対象サーバは全てnginxで動いてます。設定を見てみましょう。

$ grep ssl_certificate /etc/nginx/conf.d/mvrck.conf 
    ssl_certificate /etc/ssl/nginx/_.mvrck.co.jp.crt;
    ssl_certificate_key /etc/ssl/nginx/_.mvrck.co.jp.key;

ふむふむ /etc/ssl/nginx/ 以下に置かれているようですね。

ここで_.mvrck.co.jp.keyなんて作ってないぞ と思いましたがssl_certificate_key ... なるほど 秘密鍵??

疑問に思いながら最初の方に作成した秘密鍵 private_key.pem をリネームして設置しました。

$ mv private_key.pem _.mvrck.co.jp.key

また権限がnginxにしないとうまいこと動いてくれなかったのでchown nginxで設定します。

よしこれでとりあえず$ nginx -t で確かめてみよう!

$ nginx -t
Enter PEM pass phrase:

秘密鍵の入力を求められました。パスワードを入力して

syntax is ok
test is successful

と設定自体はよさそう。restartさせてみる。

$ /etc/init.d/nginx restart
 * Checking nginx' configuration ...
Enter PEM pass phrase:                                                                                                                                                                                                 [ ok ]
 * Stopping nginx ...                                                                                                                                                                                                  [ ok ]
 * Starting nginx ...
Enter PEM pass phrase:   

と,これでweb上で確認するとしっかり更新されていました!

このEnter PEM pass phrase:ですが毎回入力せずにも設定できるようです。

例としてパスフレーズを聞かれないようにするには

$ openssl rsa -in private_key.pem -out _.mvrck.co.jp.key
Enter pass phrase for private_key.pem:
writing RSA key

とすることで_.mvrck.co.jp.keyというパスフレーズなしのkeyが作成されます。

設置し直して$ nginx -t等で試してみるとパスフレーズなしでもできました。

私はweb上でssl証明書をみて更新されていると確認したのですがcuiでも出来るよということで教えてもらいました。

$ openssl s_client -connect www.mvrck.co.jp:443 < /dev/null 2> /dev/null | openssl x509 -noout -startdate -enddate

これで無事更新完了です。

無事といいつつ実はCommon Nameを間違えて発行して色々あったのですがそれはまた別のお話.....

ScalaMatsuri 2016

みなさんこんにちは!
去る1月30(土)1/31(日)に行われたScalaMatsuri 2016を弊社で協賛させていただきました!
簡単にご紹介させていただきます 😘

ScalaMatsuriとは

日本で最大規模のScalaカンファです。
今年で2度目(Scala Conference in Japan 2013を含むと3度目)の開催となります。
弊社では去年度から協賛させていただいております!🍻

カンファの様子 👀

国際カンファだけあって参加者が多国籍な様相でドキドキしました!
昨年に引き続きニコニコ生放送でメイントラックの中継も行われていましたね。
知人がニコニコ生放送で視聴していましたが、プレミアム会員でないとなかなか見られない状況が続いてたそうです。
当日は日本のTwitterハッシュタグがトレンドにあがっていましたねー!関心の高さが伺えます。
Scalaに興味はあるけれどご存知なかった方、予定の都合がつかなかった方、遠方等で参加できなかった方などは是非公開されている情報を振り返って現地の空気を感じていただけたらと思います。

ざっくり情報ぴっくあっぷ

  • ScalaMatsuri ページ下部のプログラムのところに登壇者のスライドが公開されております。
  • ScalaMatsuri 行動規範 可愛いと評判だったカンファの行動規範紹介動画。とりさんかわええ・・・。


行動規範マナー動画 in ScalaMatsuri 2016

このひと

感想

雑ですまん 💀💀💀
リファクタ系のお話しはなるほどみを感じながら拝見させていただきました!

  • Refactoring in Scala @gakuzzzz Phantom Typesしらんかったぜ・・・!
  • Scala Refactoring @tomerg スコープ短い値だとTupleでえいやとかやっちゃいますけれど。あんまり横着せずにcase class定義しろよって話ですね。 1行に処理書きすぎると何やりたいのかわかんねーから加工中の中間データに名前つけてあげましょうはさじ加減どんなもんなんでしょね。 コード量少ない方がいいんじゃないかなんて処理詰め込みがちですけれど、あとで見てわかるようにほどほどにしないとですね。
  • dwangoさんのLTで発表されてた新卒向け研修資料これ力作ですねー! 私もScalaに触れ始めて思いましたけれど書籍だけだと導入部の学習が難しくてなかなか苦労しました。
  • 2日目のアンカンファレンスで大学教育とScalaとのお題目で一部大学の情報系学部向けカリキュラムでScalaを取りいれたとの話がありましたが、今は大学でもこういうの教える時代なのですね。専門用語が含まれると理解の度合いが落ちるってあたりがあるあるでした。仕事にも通じますが相手に噛み砕いて伝えることは意識しないと忘れてしまうところがあるので気をつけないと。

あとあと・・・!

f:id:maverick-techblog:20190801144146j:plain

弊社でブース出させていただきました!!
怪しげなクイズの紙を配布させていただいたのですけれど、ご覧になられた方いらっしゃいますかねー。

f:id:maverick-techblog:20190801144148p:plain

私もちょっと挑戦してみたのですけれどあきらめました٩(๑´3`๑)۶
今年のScalaMatsuri参加できなかったのでこの辺面白かったぜとか教えてもらえると嬉しいです 💋

サーバからslackへ通知する

2回目の登場のあすてるです。 サーバ上で叩いたコマンドっていつ終わるかわからないものがありますよね? それらを完了したらslackで通知させようと思った話です。

slackでの設定

incoming hookを使用します。 https://my.slack.com/services/new/incoming-webhook にアクセスします

今回はランダムに送る程で Choose a channek... で #randam を選択して 下のAdd Incomingg WebHooks inteagration を押して進みます。

Webhook URLというものが出てくるのでこれをメモしておきましょう。 例として下記がでてきたとします。 https://hooks.slack.com/services/T0hogehoge/B0piyopiyo/HogePiyofooooooooooo

一番下の Save Settings を押して保存します。 これでslack上での作業は終了です。

サーバでの設定

ここからはサーバ上での操作です。 適当な場所にスクリプトを置きます。

#!/bin/bash
url='https://hooks.slack.com/services/T0hogehoge/B0piyopiyo/HogePiyofooooooooooo'
username='end_command'

to="$1"
message="$2"
emoji=':end:'

payload="payload={\"channel\": \"${to}\", \"username\": \"${username}\", \"text\": \"${message}\", \"icon_emoji\": \"${emoji}\"}"
curl -m 5 --data-urlencode "${payload}" $url

urlには先ほどメモしたWebhook URLを入れてください。 chmod 755 などして実行権限をつけておくと良いでしょう。

やってみる

第1引数にuser名かChannel名をいれて第2引数に通知したいコメントを入力します。 例

$ ./slack.sh "@astel" "test-message"
ok%
$ ./slack.sh "#random" "test-message"
ok%

okと表示され、メッセージが届いていることが確認できました。 aliasなどで設定しておくと便利かもしれません。

実際は&&で渡して使用します。

おわりに

簡単にセットアップできました。 userのアイコンを今回は:end:にしましたが自分で設定して面白いものもつくれそうです。 zabbixの障害通知やcronでセットしてbotなどにも使えそうですね。 問題としてはそもそも通知する前にこけてしまった場合は && だと動かないのでそこらへんは考える必要がありそうです。