ソモサン

私rohkiによる活動や読書の記録をつらつらと書くページです

(追記あり) AWS Lambda が正式に Rust 対応したので KinesisFirehose にくっつけて性能計測した #rustlang #rust_jp

これは Rustその2 Advent Calendar 2018 の 2 日目の記事ということにしました。

結果(2018/12/3 更新)

f:id:rohki:20181204083205p:plain

github.com

ありがたい PR のおかげで更新できました! tatsuya6502 さんありがとうございます!
246k record を 57.6 秒で処理したので、4,270 record/sec でなんと 4,000 の大台突破です。
修正内容は僕がサボったところを指摘いただいた感じでした。むしろすみません。

Runtime record/s
Python 142.8
Rust(on Python 2.7) 2348.6
Golang 3910.0
Rust(on Custom Runtime) v1 3967.2
Rust(on Custom Runtime) v2 4270.0

ということで、4,000 を越えました!すごい!!

成果物

github.com

これまでのあらすじ

rohki.hatenablog.com rohki.hatenablog.com

初めて Kinesis Firehose で計測してから 1 年弱、 Golang で計測してから 10 ヶ月弱…

aws.amazon.com

ついに正式対応ですよ! なんかもっとすごかったけど!! ということで、ここまできたら計測せねばということでやりました。

実装

去年に作ったやつを適当にちょちょっと書き換えたら動きました。時間は 2 時間ぐらい?
この辺は数をこなしたおかげかと思っとります。

せっかく linux 環境作ったし、そのまま build したやつ動くかなーとやってみた結果、llibc のバージョンで怒られました…。

静的リンクすればいいんんだろう、ということで musl 版を以下を使って作りました。 rohki.hatenablog.com

コマンドは以下です。

cross build --target x86_64-unknown-linux-musl --release

計測環境

  • Runtime: Custom Runtime
  • メモリ: 128 MB

いつも通りの下限性能です。
これまでと同じどおり、Kinesis Data Generator で Apache Log っぽいものを投入して、時間あたりでどれだけ処理できるかをみます。

結果(再掲)(2018/12/3 更新)

f:id:rohki:20181204083205p:plain

246k record を 57.6 秒で処理しました。

Runtime record/s
Python 142.8
Rust(on Python 2.7) 2348.6
Golang 3910.0
Rust(on Custom Runtime) v1 3967.2
Rust(on Custom Runtime) v2 4270.0

と、いっても最大風速の性能がならんだ、というところです。 感覚的には 3800 から 3700 が平均性能といったところでしょうか。それでも十分速いです。

今回は速度も安定してました。毎回 4,200 record/s を安定して出していたので、予測しやすくなってます。

んで、実戦投入できる?

熱意と覚悟、Lambda のコスト削減に並々ならぬ事情があれば。
Lambda の仕様を考慮した build まわりのややこしさがどうしたって足を引っ張ります。
OpenSSL の反省を踏まえて今回の GLIBC はさくっと対応できましたが、Lambda の仕様と対応策の検討が追いつかないとひたすらハマる落とし穴でした。
動いてますがなんか見落としがありそう…やるのであれば場所を選んで少しずつって所かと思います。

さいご

ついに正式対応。感慨深い…
書いてさらしたら PR もらえた。ありがたい…

PC 新調した

f:id:rohki:20181201174248j:plain
A485 Thinkpad(OS: Linux Mint)

ひさしぶりの Linux 環境である。
Wi-Fi につまったり、日本語入力の設定につまったりしたけども、まぁ動きました。この記事も新しい端末から書いてます。

やったこと

  • Wi-Fi ドライバが認識されなかったので、ドライバを追加してビルドした
    • Secure Boot が邪魔していたので無効化した。
  • 日本語入力を設定した。iBus とか懐かしい
  • mac から zshrc を持ってきた。パスを修正すればいけた。
  • Password Manager にログインした。Dashlane 使ってるけど、Add-in だけでどうにか使える

ちょっと感動したこと

f:id:rohki:20181201175051p:plain

DuckDuckGoWi-Fi について調べたら、右側に手順が出ましたよ!
内容はフォーラムから引っ張ってきている模様。

ちょっとこまってること

TouchPad が大きいせいで、カーソルがとんじゃいます。変なところに文字入力してて辛い。
キーボードは固めかなー。そのうちなれるでしょう。

感想

パスワードマネージャと zshrc があればセットアップはわりと楽なもんですね。
次は開発環境構築。

Scala 関西 Summit 2018 でいろんな体験をしてきた #scala_ks

よかったー

新しい話をきいたり、初めて会う方とはなしたり、コントリビュートできたり、なじみの人に会えたり、同じ志向の人に会えたりしました。
まずスタッフの方と発表いただいた方、参加者のみなさん本当にありがとうございました。

2018.scala-kansai.org

印象的だったこと

Akkaを分散トレーシングで見てみよう by とーます さん

資料: http://nbviewer.jupyter.org/format/slides/github/grimrose/scala-kansai-summit-2018/blob/master/slide.ipynb#/

同志よ!(一方的)

rohki.hatenablog.com

こんな調べものをしていたのでちょーワクワクしながら待ってました。
で、聞いてる間もひたすら頷いてそだよなーという感じ。
発表の後で話した時に

  • 僕「ocjdbc 試して詰まったんですよねー…公開されてなさそうだし、Gradle だから sbt の git 指定では難しくて…」
  • とーますさん「あれはですね、最新は 0.0.2 なんですが 0.0.1 は公開されてるんですよ!」
  • 僕「!?」

とか

  • 僕「リクエスト数とサンプル数と容量の調整難しくないですか?」
  • とーますさん「そうなんですよねー」

とか。
分散トレーシングで詰まっていたところが解決したり、想定していた懸念点がその通りだったりととてもありがたかったです。
「ブログみました」もうれしかったです!書いてよかった…

OSS コントリビュート

2 日目のアンカンファレンスにて id:takezoe さんとお話しながら、サクッと GitBucket へコントリビュートできました!
本当に貴重な機会でした。

いいおっさんなのでビビらずやれって話なんですが、いかんせんビビりなもので。
コントリビュートできたこともそうですし、JavaScalaOSS 事情的なお話も興味深かったです。
経験できたってことが何より大事かなーとおもっていて、これで一つハードルを越えられた気がします。がんばってこう。

Akka と k8s

アンカンファレンスにて他の方が挙げたところに便乗して質問しました。
というのも、以前 Envoy の話を聞きました。

rohki.hatenablog.com

この時に聞いたコンセプト「アプリケーション開発者がドメインに集中できるようにする」と Akka がどこか同じような感じだなーとぼんやり思っての参加でした。
解決をどうすればよいのか、までは至れてません。そのあたりは各々の製品特性によるでしょうし。
それでも意見を上げて、反応をもらえるってのは貴重な機会でした。

全体を通して

お久しぶりですといったり、スタッフをされてた学生の方と盛り上がったり、同じことを調べてる方と情報交換したり、新しいことに挑戦できたり、2日間でいろいろなことができました。
パワーワードが生まれるところにも立ち会えましたし。
来年も参加しよう

Rust で書いた HTTP サーバーを Github 経由で Zeit へデプロイした

成果物

github.com

でけたー。面白い。

Zeit

zeit.co

クラウドベンダーの模様。
CDN やら DNS やらがある。
今回はインスタンスを作るタイプ。

Docker でデプロイ

zeit.co

Docker image でデプロイできるすごいやつです。 僕にとっては、デプロイ先のサーバーがない状態になります。
CLI 上で操作ができるよう、バイナリも用意されています。

Github との統合

zeit.co

が、今回は Github との統合を行いました。
Pull Request を送ると、すぐにデプロイが始まる感じです。

f:id:rohki:20180916111037p:plain

実際の Pull Request

こんな感じ。

ぶつかったこと

100 MB 制限

Docker image の上限が 100 MB まででした。

f:id:rohki:20180916111402p:plain

実際のログ

適当に書いた Dockerfile ではあったものの、400 MB…
ちょっと根本的に考えを変えないといけない感じです。

解決: Multi stage build

FROM ekidd/rust-musl-builder:stable AS rust-builder
ADD . .
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM alpine:3.8
COPY --from=rust-builder /home/rust/src/target/x86_64-unknown-linux-musl/release/zeit-sample /usr/local/bin
RUN apk add --no-cache ca-certificates && update-ca-certificates
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_DIR=/etc/ssl/certs
CMD ["zeit-sample"]

以下 2 つを参考にしました。

こんなことできたんすねー。知らなかった…
Multi Stage Build という手法で、ドキュメント もあります。

ビルド環境と実行環境を明確に分けることで、デプロイに使用する Docker image の内容物を最小限にできます。
上記で最終的には、6.6 MB になりました。

やってみて

✅ ポートを自動検索してくれる

最初 EXPOSE 書いてたんですが、自動で見つけてくれるらしくいらなかったです。

✅ Cold 状態からの起動が早い

5 分(だったかな?) で環境が Cold 状態に移行するのですが、そこからの起動が早いです。
Cold 状態からの起動だった場合は、上記したポートの自動検索の結果がログにでます。
でも、その差がわかりづらいです。

Github との連携が楽

自動でやってくれるのはよいですねー。
Dockerfile 書いて now.json 書いたらデプロイまでやってくれるってのはありがたい。

❌ 100 MB 制限がキツい?

環境や言語によっては、100 MB の制限は厳しい、のでしょうか? Runtime やバイナリの大きさ次第では、この制限を通過するために、あれこれと努力する必要が出てくるかも。
Golang の 1 バイナリで済むというデプロイしそうがここでも聞いてくるのかなーとか思いました。
先ほどのビルド環境と実行環境を分けることで、純粋な Linux 環境にバイナリをコピーすればおしまい的な。

おわりに

で、この文章を書いてるときに関連文書を見直してるとサンプルを見つけて、かつ Rust もあるという…

now-examples/rust-rocket at master · zeit/now-examples · GitHub

おんなじじゃん!!!
ちゃんとドキュメントというかリリースノートというか、そのあたりは読みましょう…

それはさておき、デプロイの方法とか環境情報の持たせ方とか、パフォーマンスの考え方、そして実装方法。
特に初期起動コストを支払う回数が増えるってのが違うかも。

builderscon.io 2018 に行ってきた #builderscon

はじめに

builderscon.io

いやー、最高でした!

rohki.hatenablog.com

rohki.hatenablog.com

rohki.hatenablog.com

2016年からなんだかんだ 3 年連続参加してました。
以下で特に気になったところの書きます。

IoT の闇 / id:kazuph1986 さん

SNS 禁止なのであまりかけず上記が全てではあるのですが、ひたすら頷いたり質問したりしました。
昨年に続き、こういう話が聞けるのもいいですよねー。
しかし書きたい。共感したことをひたすら書きたい。

Envoy internals deep dive / Matt Klein さん

Envoy の名前は聞いていて、役割も知っていた つもり でしたが、もっと凄まじいものでした。
ネットワークについてのありとあらゆることをやり、アプリケーションエンジニアを業務ロジックに集中できるようにする、という強い意志を感じました。

一方で、当然ながら Envoy がそのあたりのつらさを一手に引き受けることになります。
(こーれめっちゃ複雑じゃん)という感想を発表中に持っていたら、「(Envoy は)複雑なプロセスである」「利用する時はネットワークを知った上で」という発表があり、やっぱりそうだよねーと安心しました。
ネットワーク詳しくならなければ。

Understanding Microservices with Distributed Tracing / lita さん

rohki.hatenablog.com

過去にこんな調べ物をしていたりしてたので、こちらに。*1
アプリケーションを開発する方が追跡性の注入を意識しなくていいようにする、を徹底されててすごいなって感じでした。
Envoy の話を先に聞けていたが故に、内容がするっと入ってきました。良い構成したよ!

こちらでは質問しました。内容は「分散トレーシングで DB アクセスをつなげて出すためには、Envoy 経由になる? それとも DB の手前に簡易サーバーをおく?」 でした。
答えは Envoy 経由!まじか。
そしてそのあとの懇親会で、「でもコードを書き変えないで追跡の親子関係の伝播どうやるんだろう」で盛り上がりました。 で調べてみると、Trace context propagation に行き当たり、「入力コンテキストから取り出して、出力に注入する」とか書いてありそう。
まじか…まじなのか。いったいどうやってるんだろう

lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話 / Rui Ueyama さん

内容の多くは PodCast でお聞きしていたのですが、知らなかったところへ質問した結果、自分の考え方との差分に気がついた、というところでした。
質問は、「既存ツールとのバイナリ差分をなくすことを第一のゴールとしなかったのは、増分開発する方がよかったからですか?」でした。
それに対する回答は以下 3 つです。

  • (バイナリ差分をなくすことは難しい) *2
  • 仕様をベースに作り、自分で読み込んで調査することで学習になる
  • カーゴ・カルト絶対に やらない

特に最後にハッとさせられました。
確かに「調査する時間がないから詳細はわからないけどおまじないとして入れる」とかやっちゃってるんですよね。
でもそれってもしかしたら無駄なものかもしれないのに、全然改善できずそのまま次の人に渡すことになるわけで。
調べ切れる時間を作る、腕を持たねば。

終わりに

振り返ると、もうちょい聞きに行くものの幅を増やした方が良さそうだなーと感じました。
k8s とか microservice あたりに心惹かれたものの、いやそれ聞くなら別の機会があってその時にしよ、と意図的に避けたりしました。が、それでもちょっと実利に寄りすぎな感じでした。

もしくは自分で発表するかか!何をだろ。
Rust と WebAssembly のやつは久しぶりに遊び要素が多めなのであの発展ですかねー

本当にすごいエネルギーであの場を作っていただいていて、感謝しかない感じです。
ありがとう! builderscon!!

*1:OpenCensus でのブログ流入が増えてました。そらそうだ。

*2:3番目に絡むためカッコがき

Rust で WebAssembly と戯れてどうにもならなかった

夢の跡

github.com

頑張ったけど無理でした。
やりたかったことは WebAssembly を使った音声操作です。

動機

Rust 実装の音声ライブラリについて調べてたところ、cpal に行き当たりました。
んで、何とそこには Emscripten という文字が。
お? ブラウザでも動くの?? となぜか WebAssembly で書いてみようと思ったのです。
制限時間は1日。

いや対応してないやん

それはそうなのですが、WebAssembly で動くと

  1. 必要な速度が出れば、ブラウザ上で音声規格の配布ができる
  2. 上記が達成できれば、標準化を狙った規格の試行錯誤ができる
  3. 標準化にこぎつけブラウザに組み込むとなった段階で、組み込める(かもしれない)コードが出来上がってる

こう書くと、風が吹けば桶屋が儲かる感がすさまじい。おかしいな、こんなはずでは…
それはともかく、速度を優先されてかつ私がすぐ試せるものとなると、大学時代にかじった音声処理でした。
ので、それでお試し。

やったこと

まずはネイティブ実装

#[no_mangle]
pub extern "C" fn hoge() -> f64{
    let device = rodio::default_output_device().unwrap();

    let sink = Sink::new(&device);
    // Add a dummy source of the sake of the example.
    let source = rodio::source::SineWave::new(440).take_duration(Duration::from_secs(3));
    sink.append(source);
    sink.sleep_until_end();

    1.0
}

サイン波を3秒再生する実装です。
使用したライブラリは rodio で、ドキュメントとかを見ながら実装しました。

ローカルでの WebAssembly の実行環境

nmi.jp

上記ページを参考させてもらいました。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <script>
            fetch('../target/wasm32-unknown-unknown/debug/wasm_sample.wasm')
                .then(response => response.arrayBuffer())
                .then(function(bytes) {
                    return WebAssembly.instantiate(bytes, {});
                })
                .then(results => {
                    console.log(results.instance.exports.fuga(2));
                });
        </script>
    </head>
    <body>
    </body>
</html>

IntelliJ で HTML を書いて右上にマウスカーソルを動かすと、HTTP サーバーとして動いてくれます。
ので、今回はそれを使いました。お試しなので許して。

WebAssembly 呼び出せてるよなー、の確認で加算関数を作って呼び出し。
無事呼び出せたので次。

ImportObj の定義

いよいよ音声再生だーと呼び出してみたところ、即失敗。
WebAssembly.instantiate にてエラーが発生していて、エラーメッセージは以下でした。
TypeError: import object field 'env' is not an Object

env とは???
で、エラーメッセージで調べてみると、以下を見つけました。

Rocket - A Rust game running on WASM

なるほどなー、足りない関数の対応表を渡してやる必要があるわけですな。
ということで、そのままパクる。
関数の呼び出しに成功しました。んが!

WebAssembly 環境の追加

動きませんでした。 そらそうだよね!
print デバッグをやって確認したところ、1行目でこけてる模様。
rodiocpal をローカルにクローンして、書き換えながら確認しました。

#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd",
              target_os = "macos", target_os = "ios", target_os = "emscripten")))]
use null as cpal_impl;

use std::error::Error;
use std::fmt;
use std::iter;
use std::ops::{Deref, DerefMut};

mod null;
mod samples_formats;

#[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[path = "alsa/mod.rs"]
mod cpal_impl;

#[cfg(windows)]
#[path = "wasapi/mod.rs"]
mod cpal_impl;

#[cfg(any(target_os = "macos", target_os = "ios"))]
#[path = "coreaudio/mod.rs"]
mod cpal_impl;

#[cfg(target_os = "emscripten")]
#[path = "emscripten/mod.rs"]
mod cpal_impl;

引用元 cpal/lib.rs at 77ce5eba06447275ce6a094ac053e6775272ad8c · tomaka/cpal · GitHub

当たり前なのですが WASM の文字がなく、結果として未対応環境の null に行きついてエラーになってました。そらそうだ。 同じブラウザで動かすし、内部で使われてる stdweb は WebAssembly 対応してるぽいから、emscripten の記載部分に wasm 追加すればいいかなー、と安直に考えて追加しました。
中を見たところ WebAudio に対応してるかを判定してたので、WebAssembly から API 呼び出しできればいけるかなーってところでした。

追加方法の参考リンクは以下です。
https://stackoverflow.com/questions/48350087/how-do-i-conditionally-compile-for-webassembly-in-rust
https://github.com/koute/stdweb/blob/master/src/webcore/ffi/mod.rs

記載方法はこんな感じ。

#[cfg(any(target_os = "emscripten", all(target_arch = "wasm32", target_os = "unknown")))]
#[path = "emscripten/mod.rs"]
mod cpal_impl;

Cargo.toml も併せて修正。

[target.'cfg(any(target_os = "emscripten",all(target_arch = "wasm32", target_os = "unknown")))'.dependencies]
stdweb = { version = "0.1.3", default-features = false }

んでなんと通ってしまった!
そして次のエラー。

sinf/cosf の補填

突破だーと思ったところ、またもやエラーでした。
LinkError: import object field 'sinf' is not a Function
なるほどな? 関数が見つからん、といってるので、先ほどの関数の対応表に sinfcosf を追加して、突破です。
で、次のエラー。

LinkError: "import object field 'emscripten_asm_const_int' is not a Function" にて挫折・終了

軽く調べて C の header ファイルに行きついた瞬間に、これはちょっとこれまでのやつと違う気がする、となってきました。
C から Javascript の関数を呼び出すための関数、らしいのですが、今そういったことをやってないんですよね。
関数の役割的に sinf のように補填すればよい、ということでもなさそうです。

んで、stdweb の実装に確かに記載をみつけ、mod.rs での切り替えや macro.rs での呼び出しも見つけましたが、よくわからんぞーとなってきて心折れてきたところで夜でした。

難しい

そんな簡単にいくわけがない、とわかっていたものの、中途半端になってしまいました。
いつかリベンジ。いつか…

Kinesis Stream の中身を追いかけて出力する k-iter に verbose mode を付けた

github.com

0.3.0 でございます。

呼び出し方と形式

--verbose を付ける。以上!

k-iter --verbose -n sample-stream -r ap-northeast-1

形式

JSON で出してます。
既定ではデータを UTF8 の文字列としてだします。

{"ApproximateArrivalTimestamp":1533704447.987,"Data":"155.103.165.228 - - [08/Aug/2018:14:00:47 +09:00] \"DELETE /list\" 200 3568 \"-\" \"Mozilla/5.0 (Windows NT 6.0; Win64; x64; rv:5.8) Gecko/20100101 Firefox/5.8.6\"\n","PartitionKey":"9612982382","SequenceNumber":"49587101216856299235294266286047359784703484607321866242"}

背景

Kinesis Stream の中を追跡する際には、そのデータがいつ投入されたのか、ってのも大事かと思います。
データ生成のタイムスタンプとデータ投入のタイムスタンプを比べたい感じです。
データ生成は自分たちで埋め込んで頑張りますが、データ投入は Kinesis Stream のレコード情報にあるので、そちらを利用したいです。

jq などで整形・加工しましょう、ってスタンスです。これ以上は頑張らないです。

現在のオプション

--help で確認できるオプションは以下のような感じ。
--iterator-type 増やしたり、--data-format 追加したりもしてました。

USAGE:
    k-iter [FLAGS] [OPTIONS] --region <NAME> --stream-name <NAME>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information
        --verbose    Enable verbose mode.

OPTIONS:
        --data-format <TYPE>       Set data output-format. [default: UTF8_STRING]  [possible values: RAW_BYTES,
                                   RAW_STRING, UTF8_STRING]
    -t, --iterator-type <TYPE>     Sets iterator type. [default: LATEST]  [possible values: LATEST, AT_SEQUENCE_NUMBER,
                                   AFTER_SEQUENCE_NUMBER, AT_TIMESTAMP, TRIM_HORIZON]
    -r, --region <NAME>            Sets a region name. [possible values: ap-northeast-1, ap-northeast-2, ap-south-1, ap
                                   -southeast-1, ap-southeast-2, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu
                                   -west-3, sa-east-1, us-east-1, us-east-2, us-west-1, us-west-2, us-gov-west-1, cn
                                   -north-1, cn-northwest-1]
        --sequence-number <NUM>    Set Sequence number when Iterator Type is AT_SEQUENCE_NUMBER or
                                   AFTER_SEQUENCE_NUMBER.
    -s, --shard-id <ID>            Set shard id. [default: shardId-000000000000]
    -n, --stream-name <NAME>       Sets a stream name.
        --timestamp <TIMESTAMP>    Set timestamp(UNIX Epoch milliseconds) when Iterator Type is AT_TIMESTAMP.

つづき

終了条件の指定、とかですかねー。ある条件に到達したらプロセスを終了する的な。

  • N レコードとったら
  • ある日付に到達したら
  • あるシーケンスナンバーに追い付いたら
  • 先頭に追い付いたら

できそうなのはこんなもんでしょうか?
あとは複数シャードもかな?