PHP の Guzzle で直列 vs 並列
前やった node.js の Promise で直列 vs 並列を php でやってみた。 素の php だと非同期処理はめんどくさいので 今回は Guzzle を使ってみた。
Guzzle は非同期もできる http クライアントだけど、 Guzzle Promises を使えば 一般的な処理も非同期で書けるみたい。
<?php /* * PHP の Guzzle で直列 vs 並列 */ require './vendor/autoload.php'; function createUrl(int $sec) { return 'http://httpbin.org/delay/' . $sec; } function serial(array $urls) { $client = new \GuzzleHttp\Client(); $bodies = []; foreach ($urls as $url) { $response = $client->request('GET', $url); $bodies[] = $response->getBody()->getContents(); } return $bodies; } function parallel(array $urls) { $client = new \GuzzleHttp\Client(); $promises = []; foreach ($urls as $url) { $promises[] = $client->requestAsync('GET', $url); } $responses = \GuzzleHttp\Promise\all($promises)->wait(); $bodies = []; foreach ($responses as $response) { $bodies[] = $response->getBody()->getContents(); } return $bodies; } function main() { $urls = []; foreach (range(1, 4) as $sec) { $urls[] = createUrl($sec); } $startTime = new \DateTime(); serial($urls); $diff = $startTime->diff(new \DateTime()); var_dump('serial: '. $diff->format("%s sec")); $startTime = new \DateTime(); parallel($urls); $diff = $startTime->diff(new \DateTime()); var_dump('parallel: '. $diff->format("%s sec")); } main();
動かしてみる。
$ composer require guzzlehttp/guzzle $ php main.php /path/to/main.php:50: string(14) "serial: 11 sec" /path/to/main.php:55: string(15) "parallel: 4 sec"
ちゃんと並列で動いてるみたい。
※php 7.2.4 で確認
AWS の ECS & Fargate でゆるいバッチ環境を作る
仕事で以下のようなバッチを作る事になったのでその記録をメモしとく。
- 月 1 回実行
- 内容は手動でやってた作業を自動でやって結果をメールするだけ
- 実行には 30 分ほどかかる
- 全然クリティカルじゃない
- エラーが発生しても連絡があってから再実行すればよい
- 再実行もすぐでなくてもよい
で、考えた事がこちら
- 運用工数かけたくない
- cron 等で定期実行
- サーバレス
- そんなに変更ないだろうからデプロイは自動でなくてよい
- なるべく安くすませたい
AWS でいくつかやり方があるけど、どれがいいか検討してみる。
- Lambda + ClowdWatch Events
- サーバレス
- デプロイは zip 上げるだけ
- cron 風の記述で定期実行可能
- 実行時間の上限が 5 分なので今回は使えず
- Elastic Beanstalk の worker 環境
- デプロイ、切り戻しが楽なのはよい
- docker も使える
- cron 風定期実行可
- ただし、安くもないし、サーバレスでもない
- t2.micro でも一ヶ月で 1000 円強かかる
- 一旦保留、他の方法でダメだった時はこれで
- AWS Batch
- サーバレス
- 裏は docker なので使える技術はわりと自由
- 定期実行はあまり得意ではないみたい
- ECS + EC2
- ECS + Fargate
- docker
- Fargate なので使った分だけ課金
- 東京リージョンにない
- 注: これやった当時はまだでした
- Fargate はまだ ScheduleTask で使えないみたい
で、安さと運用を考えて ECS + Fargate で行くことにした。
SES の設定
今回は送信制限を解除してない SES を使ったので、 予め送信先のメールアドレスを登録しておいた。
プログラム作成 & docker 化
バッチとして実行するプログラムを作る。 docker なので言語は自由に選べばよいが、今回は node.js で。 メールは aws-sdk 経由で SES で送る。
プログラムができたら Docker 化する。 Fargate は pull が遅いらしいんで、軽量化のため ベースイメージは node の alpine にした。
ECS の設定
Fargate & SES が使える us-east-1 に構築する。
- ECR にバッチのリポジトリを作って Docker イメージを push する。
- クラスタの作成
- タスク定義
- 実行するイメージとかロール、メモリ、CPU を設定する
- ECS のコンソールから
- 「新しいタスク定義の作成」を選んで後はよしなに
- コンテナは先程追加したやつを選択。
- スケジュール設定
- ScheduleTask は使えないみたいなので、cron でやる事に。
- なので、↓を参考に cli で起動できるようにコマンドを作る
- https://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html
- 開発サーバなど、常時起動してるサーバの cron に作ったコマンドを設定する
まとめ
という事で、ECS & Fargate を使って いい感じのバッチ環境を作ることができた。 cron もサーバレスにしたければ Lambda & ClowdWatch Events で いけるかもだけど、力尽きたので今回はここまで。
補足
今回はバッチ 1 つだけだったんで Fargate を選択したけど、 バッチサーバ立てるくらいの規模なら クラスタを EC2 で組んで ScheduleTask にした方がよいかも。
ただし、pull してから実行までに少しタイムラグがあるので、 起動時刻に厳密なケースでは他の方法の方がよいと思われる。
追記
現在は
- 東京リージョン
- 時間指定実行
に対応したるので、さらに使いやすくなった。
typescript の async/await で直列 vs 並列
typescript でちょっとやってみました。
http://httpbin.org/delay/{delay}
というのは delay 秒待ってからレスポンス返してくれる URL です。
/* * serial vs parallel */ import request from 'request-promise-native'; function createUrl(sec: number): string { const secStr: string = String(sec); return `http://httpbin.org/delay/${secStr}`; } async function serial(urls: string[]): Promise<string[]> { let results = []; for (const url of urls) { const ret = await request(url); results.push(ret); } return results; } async function parallel(urls: string[]): Promise<string[]> { const promises = []; for (const url of urls) { const promise = request(url); promises.push(promise); } const results = await Promise.all(promises); return results; } async function main() { const range = [1, 2, 3, 4]; const urls = range.map(createUrl); let startTime = Date.now(); let results = await serial(urls); console.log('serial'); // 1 + 2 + 3 + 4 = 10 秒かかる console.log((Date.now() - startTime) / 1000); startTime = Date.now(); results = await parallel(urls); console.log('parallel'); // max(1, 2, 3, 4) = 4 秒かかる console.log((Date.now() - startTime) / 1000); } main();
動かしてみる。
$ mkdir test; cd $_ $ pbpaste > index.ts $ npm init -y $ npm i -S typescript @types/node $ npm i -S request @types/request $ npm i -S request-promise-native @types/request-promise-native $ npx tsc --init $ vim .tsconfig # target を es2017 に $ npx tsc $ node -v v8.4.0 $ node ./index.js serial 11.58 parallel 4.373
ちゃんと並列で動いてますね。
コマンドラインで json を作る
json をパースするツールとして jq というのがありますが、 その反対、json を作るツールとして jo というのがあります。
使い方
引数でキーと値を指定する
$ jo key=value {"key":"value"}
配列を作るときは -a オプションをつける
$ jo -a 1 2 3 [1,2,3]
ネストしたオブジェクトを作る場合は $() を使う
$ jo key=falue object=$(jo name=test value=1) {"key":"falue","object":{"name":"test","value":1}}
-p オプションをつければ見やすくしてくれる
$ jo -p key=falue object=$(jo name=test value=1) { "key": "falue", "object": { "name": "test", "value": 1 } }
標準入力からのデータも使える
$ seq 1 10 | jo -a [1,2,3,4,5,6,7,8,9,10]
インストール
brew からでも入るみたいだけど、 ソースからコンパイルした。
git clone git://github.com/jpmens/jo.git cd jo autoreconf -i ./configure make check make install
fselect で SQL ライクにファイルを検索する
fselect という ツールを見つけて、面白そうだったんで触ってみた。
主な機能はこんな感じ
早速インストールして fselect のリポジトリで試してみる。
$ fselect name, from ./ where name = '*.rs' lexer.rs main.rs mode.rs parser.rs searcher.rs util.rs $ fselect name, from ./ where name = '*.rs' limit 2 lexer.rs main.rs $ fselect size, name from ./ where name = '*.rs' limit 2 into csv 8890,lexer.rs 1598,main.rs
結構複雑なクエリも使えるみたいだし、 毎回 find の使い方を検索するよりいいかもしれん。
惜しいのは order by が使えない事だけど issue には上がってるんで、そのうち実装されるんではないかと期待。
Elixir がよかった件
昨年は いま学ぶべき第二のプログラミング言語はコレだ! 未来のために挑戦したい9つの言語とその理由 の記事がきっかけで Rust, Haskell をやって非常によかったので この流れで残りの Elixir もやってみた。
テキストはもちろんこれ 最速で知る! ElixirプログラミングとErlang/OTPの始め方【第二言語としてのElixir】
で、やってみた結果、
- ゆるい(非純粋)関数型
- なので末尾最適化できる
- 関数定義にパターンマッチ、ガードが使える
- ゆるいので変なコト書いても怒られない
- 動的型なので気楽に書ける
- map のリテラルがある
- return, セミコロン書かなくていい
な点が非常によかった。 さらに新しい言語っぽく
- repl
- パッケージマネージャ
がちゃんとあるんで、 普段使いの言語としていい感じに使えそう。
もちろん並列処理が得意というのもいいんだけど、 それなしでもよさげ。
なので、しばらくこれ使ってみる事にする。
色々な言語で偶数自乗和 その2
TL;DR
java8
import java.util.stream.IntStream; public class Main { public static Integer evenSquareSum(Integer n) { return IntStream.range(1, n) .filter(n1 -> n1 % 2 == 0) // variable n is already defined in method evenSquareSum(Integer) と出るので変数名を変更。引数と同じ変数名は使えないみたい .map(n1 -> n1 * n1) .reduce(0, (acc, n1) -> acc + n1) ; } public static void main(String[] args) throws Exception { Integer result = evenSquareSum(10); System.out.println(result); } }
ちょっと見ない間にだいぶマシになった印象。 でも SIer とかだとバージョンアップできない所も多いんだろうな…
kotlin
fun main(args: Array<String>) { val result = evenSquareSum(10) println(result) } fun evenSquareSum(n: Int): Int { return (1..n) .filter{ n -> n % 2 == 0 }.map{ n -> n * n }.fold(0) { acc, n -> acc + n } }
よいです。
swift
func evenSquareSum(n: Int) -> Int { return (1...n) .filter { $0 % 2 == 0 } .map { $0 * $0 } .reduce(0) { $0 + $1 } } let result = evenSquareSum(n: 10) print(result)
クロージャの省略記法いいね。
haskell
evenSquareSum n = sum . map (\n -> n * n) . filter even $ [1..n] {- これでも OK evenSquareSum n = foldl (+) 0 $ map (^2) $ filter even [1..n] -} main = do let result = evenSquareSum 10 print result
やっぱり右からになるのね と思ったら、自分でパイプライン演算子が定義できるみたい via * HaskellでElixirのようなパイプラインで記述したいとき - Qiita * Hakellでパイプライン演算子
(|>) :: a -> (a -> b) -> b (|>) a f = f a -- a |> f = f a evenSquareSum n = [1..n] |> filter even |> map (\n -> n * n) |> foldl (+) 0 main = do let result = evenSquareSum 10 print result
すごい