バベルの図書館は完成しない

Extended outer memory module
for my poor native memory.

Posts:
2020/09/28 gendoc という YAML からドキュメントを生成するコマンドを作った
2020/09/13 ISUCON10 の予選を 7 位で通過した
2019/12/01 Puma の内部構造やアーキテクチャを追う
2019/05/27 Golang の正規表現ライブラリの処理の流れをざっくり掴む
2019/04/29 InnoDB の B+Tree Index について
2019/04/29 InnoDB における index page のデータ構造
2019/04/28 InnoDB はどうやってファイルにデータを保持するのか
2019/01/06 Designing Data-Intensive Applications を読んでいる
2019/01/03 年末年始に読んだ本について、など
2019/01/01 Ruby から ffi を使って Rust を呼ぶ
2018/11/10 ブラウザにおける状態の持ち方
2018/07/01 Rust で web アプリ、 或いは Rust における並列処理
2018/05/14 なぜテストを書くのか
2018/05/13 Rust で wasm 使って lifegame 書いた時のメモ
2018/03/12 qemu で raspbian のエミュレート(環境構築メモ)
2018/03/12 qemu で xv6 のエミュレート(環境構築メモ)
2018/03/03 Ruby の eval をちゃんと知る
2018/02/11 Web のコンセプト
2018/02/03 Rspec のまとめ
2018/02/03 Ruby を関数型っぽく扱う

Rust で web アプリ、 或いは Rust における並列処理

Rust ね

最近仕事以外だとよく Rust を書いている。

もともと systems programming 的領域で C++ を置き換えるべく(?) Mozilla によって開発され、現在は Firefox において利用されている言語だ(???)

この紹介はかなり適当だし、僕は今気分的に Rust の正確な起源にあんまり興味がないので、気になる人は適宜調べてほしい。

言語の仕様的な特徴としては、 Rust はメモリ安全を意識して書かれており(C++ を用いた開発からの学びなのだろう)、どのデータをどうやって扱っているかを正確に記述していくことができる。

またコンパイル時には、強い静的型付け言語らしく型の整合性をチェックしてくれるし、上記に書いたような記述を守ることによってメモリ安全性を壊すような操作がないかもチェックしてくれる。

さて

普段は Ruby on Rails で web アプリケーションを書いているのだけど、実行時まで気づかないエラーや不整合があることに度々遭遇してかなりつらい気持ちになってきたので、プログラマ側が記述量を増やしてそれをコンパイル時に静的解析してもらうことで、なるべく実行時に変なことになるのを防ぎたいという気持ちになってきた。

ただ今すぐに systems programming 的な低めのレイヤで何かしたいかと言われるとそういうわけでもないので、間を取って Rust で Web アプリケーション書いてみようかなって思った。

その過程で色々学んだので書き残しておきましょう、という記事。

Rust で Web アプリ

非常に簡単な仕組みのアプリケーションを書いた。

自分のサーバから他のサーバに対してリクエストを投げ、
返ってきたレスポンスをパースして整形
自分のサーバで html を生成して表示

みたいなやつ。

ソースコードはここ

Rust で Web アプリを書いてみて

普段 Rails で Web アプリを書いているんだけど、

Web アプリ書くのには 動的言語で動的ディスパッチとかやりたくなるのもわかるなあと思った。

今回僕が書いたアプリケーションはあまりにもシンプルなので、あんまり Rust であるメリットはなかった。

===

ただ、安全に処理を行いたい。複雑な処理を正しく行いたい。

みたいなモチベーションが出てくると、 Rust みたいな言語を利用するのは悪くないと思います。

処理を分散させはじめたり、トランザクション処理をおこなったり、少ないリソースでアプリケーションプロセスを走らせたり、ちょっと気合い入れて工夫を始めると、 Rust は悪くなさそう。(書くの自体は結構楽しかったw)

特にめんどくさかった(めんどくさそうな)ところ

なんかこう、サーバーサイドのフロントよりの部分というよりは、もうちょっとサーバー側よりの、SQL の実行のためのラッパー的な部分とか、リクエスト要求を受け取って実行していく部分とか、何かを根っこからいじりたい時に部分的に導入するのがいいのかな。

あるいは WebAssembly での利用だと思うんだが、僕はフロントがさっぱりわからないので一旦スルーした。

javascript を書かない web アプリに挑戦してみてもいいな。

リクエスト投げるのが遅い…

API の事情で N+1 的な API を投げないといけない箇所があり、そこがかなり遅い。

そこで http request を並列化し、全部バラバラに投げた上でどっかで待ち受けたいなと思った。

そのまんまのコードじゃないけれど、やりたかったのは以下みたいな感じ。

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use std::sync::mpsc;

pub fn concurrent_with_channel() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));

    let (tx, rx) = mpsc::channel();

    for i in 0..10 {
        let (data, tx) = (data.clone(), tx.clone());

        thread::spawn(move || {
            // 対象のデータを Mutex を使って lock して競合を回避することで安全性を保証
            // (一応ヒューリスティックに被らないようにはしてるけど)
            let mut data = data.lock().unwrap();

            // 例えばたまに遅い処理が混じってても、rx.recv() のところで全部が揃うのを待ち構える
            if i%3 == 0 {
                thread::sleep(Duration::from_millis(100));
            }

            // 今回は +1 するだけのシンプルな処理だけど
            // 本当はここで http request を投げて結果をもらってごにょごにょしたい
            data[i] += 1;

            // 最後に channel から処理が終わった合図ということで () を送信する
            tx.send(()).unwrap();
        });
    }

    for _ in 0..10 {
        rx.recv().unwrap();
    }

    println!("{:?}", data);
}

がしかし、これは上手く行かず。

どうもリクエストを投げるために利用している reqwest っていうシンプルな crate(ライブラリ)だと

並列で投げずに直列化しておこなっていくっぽい??

(N + 1 の N を半分にしたらかかる時間も綺麗に半分になったので、実態としては間違いない。原因は違うかも)

ということで

機能多そうだから不要かなと思っていた tokio を使ってみることにする

書いたらまた更新する。

2018/08/04 追記

ぼーっとしていたら Rust の標準に futures が入りそうになってた 非同期処理が簡単になるといいねえ。

http://rust-lang-nursery.github.io/futures-rs/

pub fn say_goodbye() {
    println!("That's all! Bye everyone!");
}
2018/07/01 00:00
tags: rust - concurrent
This site is maintained by furuhama yusuke.
from 2018.02 -