Subscribed unsubscribe Subscribe Subscribe

Islands in the byte stream

Technical notes by a software engineer

digdag run (local mode) で並列実行数を制御する

とある分散バッチシステムでdigdagを導入してみています。

www.digdag.io

スケジューラ機能などは使っておらず、タスクを良い感じに並列実行するためのmakeよりちょっと便利なツール、くらいの感じで使ってます。

digdagは下記のようにloopなどを _parallel: true を指定するだけで並列実行できるのがいいですね。

timezone: UTC

+repeat:
  loop>: 3
  _parallel: true
  _do:
    +run:
      sh>: "echo ${i}"

+teardown:
  echo>: finish ${session_time}

ところで、このこの並列実行数を制御する方法がわからなかったのでコマンドラインオプションで渡せるようにpull-requestしました。 digdag v0.9.13 からたぶん使えます。

add --max-task-threads to `digdag run` by gfx · Pull Request #572 · treasure-data/digdag

digdag run --max-task-threads N foo.dig などとしてつかえます。まあ、このPRを出したあとに -X agent.max-task-threads=N でも指定できるとわかったのでちょっと冗長ですが、他のサブコマンド(server, scheduler)との一貫性もあるので存在意義はあるでしょう。

max-task-threadsの数によってloop taskの全体の結果はかわりません。たとえば --max-task-threads 1 のときにloopの最初のtaskが失敗すると、loopの他のタスクがすべて実行されて、loop taskそれ自体は失敗します。

検証コード:

timezone: UTC

+repeat:
  loop>: 3
  _parallel: true
  _do:
    +run:
      sh>: "if [ ${i} == 1 ] ; then false; else echo ${i}; fi"

+teardown:
  echo>: finish ${session_time}

結果:

$ digdag run --rerun mydag.dig
2017-05-28 10:35:31 +0900: Digdag v0.9.10
2017-05-28 10:35:32 +0900 [WARN] (main): Reusing the last session time 2017-05-28T01:00:00+00:00.
2017-05-28 10:35:32 +0900 [INFO] (main): Using session /Users/gfx/repo/mydag/.digdag/status/20170528T010000+0000.
2017-05-28 10:35:32 +0900 [INFO] (main): Starting a new session project id=1 workflow name=mydag session_time=2017-05-28T01:00:00+00:00
2017-05-28 10:35:33 +0900 [INFO] (0017@[0:default]+mydag+repeat): loop>: 3
2017-05-28 10:35:34 +0900 [INFO] (0017@[0:default]+mydag+repeat^sub+loop-0+run): sh>: if [ 0 == 1 ] ; then false; else echo 0; fi
2017-05-28 10:35:34 +0900 [INFO] (0020@[0:default]+mydag+repeat^sub+loop-1+run): sh>: if [ 1 == 1 ] ; then false; else echo 1; fi
2017-05-28 10:35:34 +0900 [ERROR] (0020@[0:default]+mydag+repeat^sub+loop-1+run): Task failed with unexpected error: Command failed with code 1
(snip)
0
2017-05-28 10:35:34 +0900 [INFO] (0021@[0:default]+mydag+repeat^sub+loop-2+run): sh>: if [ 2 == 1 ] ; then false; else echo 2; fi
2
2017-05-28 10:35:35 +0900 [INFO] (0021@[0:default]+mydag^failure-alert): type: notify
error:
  * +mydag+repeat^sub+loop-1+run:
    Command failed with code 1 (runtime)

なので、 max-task-threads の数は純粋にリソースの都合で調整すればよい、ということになります。

なぜTypeScript推しなのか

www.typescriptlang.org

KibelaのフロントエンドをES2015からTypeScriptに絶賛移行中です。

で、なぜflowじゃないのかって話です。

flow.org

言語仕様

言語仕様の点から言うと、決定的な差はないと思っています。

メリットもだいたい同じで

  • 生産性: エディタの補完をJSよりも賢くできるので、より少ない脳のワーキングメモリでコードを書ける
  • 堅牢性: コンパイル時に(=多くのケースではエディタで)typoなどの間違いを検出できるのでバグを減らせる
  • 学習コスト: JSをベースにしており、実行もほぼJSなのでほぼモジュールシステムと型システムだけを学べばよい

検索しやすさ・コミュニケーションのしやすさ

検索しやすさは圧倒的にTypeScriptです。

flowは検索しにくいのはもちろんのこと、その名前の元になったと思われるflow analysis との混同が激しく、flowやTypeScriptの文脈で口頭で「flow解析が云々」といってもだいたいflowのことだと勘違いされて通じません。

ファイルタイプに対する誠実さ

flowの拡張子は .js、TypeScriptの拡張子は .ts です。

拡張子でファイルタイプを判断することの多いこの世界線においては、flowがJavaScriptと同じ拡張子で互換性のない言語として書かせるのは、ファイルタイプに対する誠実さに欠けているのではないかと思っています。

もちろんマーケティング的に「flowは言語ではない。flowはJavaScriptである」と主張することは、心理的な敷居を下げるという意味では一定の効果があるかもしれません。しかし、その辺にあるコードをJSだと思って眺めているとコードハイライトエンジンやエディタが構文エラーを指摘してくる、というのは気持ちのいい体験ではありません。

開発言語

flowはOCamlです。

TypeScriptはTypeScriptです(つまりセルフホスティング)。

TypeScript処理系をJS(あるいはTS)からライブラリとして使用できるというのは大きなメリットです。

まとめ

以上です。言語としては決定的な差はないものの、それをとりまく環境はTypeScriptのほうが好ましいと考えています。ゆえに私はTypeScriptを推します。

Herokuのreview-appsが "pr-predestroy" をサポートして外部リソースの掃除をできるようになっていた

devcenter.heroku.com

Herokuのreview-appsはたとえHerokuを使っていなくても非常に便利なものですが、PR削除時にS3やElasticsearchなど外部にホストしているリソースを掃除する方法がありませんでした。

ところが、最近は pr-predestroy hookが実装されたようで、外部リソースの掃除ができるようになったみたいですね。

ますます便利になりありがたい!

mitamaeでrecipeのロードエラー時にデバッグしやすくした

一部のプロジェクトでmitamae (itamae on mruby) を使ってるんですが、自分が書いているときはともかく他人が書いているmiamaeでrecipeのロードエラーが発生すると、mruby-ioレベルでもmitamaeレベルでもファイル名を出力してくれなくてこれはデバッグできないぞという状態でした。

問題は2つのレイヤーであって、

  • mruby-io がopenの失敗時にファイル名を出力しない
  • mitamaeのロガーは読み込んだレシピを実行するときはログを吐くが、ロードの前あるいは失敗時の処理がない

低レイヤーのmruby-ioはopenは失敗時にファイ名名を出力すべきだし、miamaeレイヤーでもレシピの読み込みは非常に重要なのでもっと詳細にログに出してほしいところです。なおmitamaeレベルでのレシピ読み込みエラーはopen由来とも限らないので、どちらか一方ではなく両方のレイヤーで対応するのが一番いいと思っています。

というわけで両方なおして mitamae v1.4.5 にしてもらいました。

See Also

AndroidにおけるJava8のサポート状況 2017年版

公式ドキュメントにありました。一言でまとめると、Android Oのpreviewが出た現在においても「Android N (API version 24)と同水準」となっています。

Use Java 8 language features | Android Studio

Android Studio 2.4 preview 4 (およびそれが要求するツールチェイン)の段階では、 desugar と呼ばれるツール(実体はAndroid Gradle PluginのTransform APIによるbytecode weaving tool)によって、一度javacでコンパイルしたバイトコードのJava8の言語機能(lambda, repating annotationsなど)をJava6水準のバイトコードに変換し、それをdxコマンドでdexにコンパイルするというプロセスを経るようです。このdesugarされたバイトコードは $subproject/build/intermediates/transforms/desugar/* にクラスファイルとして残されるので、どのようにバイトコードが変更されたかは確認できます。

この変換されたバイトコードをみるかぎり、Android O相当のAndroid SDKによってサポートされた「Java8の言語機能」はretrolambdaとほぼ同じです。

件のドキュメントの表にもあるとおり、 “Java8 Language API” つまりJava8の標準ライブラリ(stream, optional, etc.)を使うには依然としてAPI level 24が必要で、状況としては一年前のAndorid Nの時点とほぼ変わりません。

つまり、Android Oのpreviewが出ている現在でも、Java8に関して去年からの唯一のアップデートは、 retrolambda相当のツールがAndroid SDKに同梱されるようになった というだけということになります *1

Android Oで java.lang.invoke パッケージが追加されていたので、minSdkVersion = O ならもしかしたらinvokedynamicを使った本物のlambdaを使えるのではないかと思ったのですが、いまのところそのようにコンパイルされることは確認できませんでした。つまり、minSdkVersionの値に関わらず、desugarが必ず入るようです。このあたりはもしかしたら正規版までに変更があるかもしれませんが。

*1:去年の時点でβだったJackは正規版になるまえにdeprecatedになったのでノーカンで。

React Reduxファーストインプレッション

今更感ありますがReact Reduxを導入したの所感をメモしておきます。

github.com

  • ざっとみてこれなら自分でも再実装できそうだなという印象
  • いままではreact-micro-container でfluxしてた
  • React Reduxにすると、個々のreact componentをfluxフレームワークに依存しない形で設計できる
    • これに対して react-micro-container はcontainerに制御されることを意識した設計になる(=containerとcomponentで設計が異なる)
    • React ReduxなしでReactを初めてRect Reduxを導入するのは簡単だし、あとから別のflux実装にするのも簡単にできそう
    • アプリケーションに小さく導入することもそこまで難しくない
  • 個々のcomponentはシンプルに設計できていいが、Reduxとの繋ぎ込みでReact Reduxが魔法のように処理するところがやや多く、かつ繋ぎこみ用のマッピングが冗長な印象はある
  • 型がない素のJSだと繋ぎこみ部分がかなり不安。flowtypeなりTypeScriptなりは導入しないといずれ破綻しそう
  • ディレクトリ構成に悩む。まだ考え中

『Androidを支える技術』(I, II) の内容に興奮した

  • 『Androidを支える技術 I』 ~ 60fpsを達成するモダンなGUIシステム ~
  • 『Androidを支える技術 II』 ~ 真のマルチタスクに挑んだモバイルOSの心臓部 ~

これらを著者の有野さん よりご恵贈いただきました。ありがとうございます。

始めて知る内容も多かったのですが、既に知っていることでも著者の意見が反映されているのを読むと、いくつものモバイルOSを見てきたハッカーからみるとこう見えるのか!という新鮮な面白さがありました。

IとIIのテーマは独立しているので、どちらから読んでもいいと思います。

以下個人的に面白かった章をピックアップします。

I の見どころ

  • §1: ActivityThread.java にあるAndroidアプリのエントリポイント public static void main(String[] args) の役割
    • ActivityTheadはデバッグしてるとたまに見ることはあるものの全然役割を知らず、それどころかここに main() があるのすら知りませんでした
  • §3: Thread, Handler, Looperあたりの話
    • HandlerとLooperまわりは難解で、なんとなく表面的に知っているだけにとどまっていました。そのあたりを詳しく解説しているのは大変ありがたい…
  • §7: バイトコード実行系の話
    • Android 7.0でARTからVM + on-demand compileに戻ったのは初めて知った!たしかにART時代のAOT compileの遅さは気になっていたので、AOSPチームのその判断自体は納得できます

IIの見どころ

  • §3: OOM Killerとの戦いは涙なしには読めませんね…
  • §4: IIで一番驚いたはなんといってもinstance stateの保存(Bundle)に関して、「BundleがSystemServerのメモリ上に保持されている」という点です。そんな事がありえるのかと思うのですが、調べたかぎりだとそうとしか思えないとのこと
  • 本作はコラムが本体かってくらいの力作で、特にモバイルOSの歴史編(勝手に命名)はAndroid, iOS以前のモバイルOSの歴史を全然知らない私にとっては黎明期の壮大な物語です。こういうのが読みたかった。

なお『Androidを支える技術』レビュアーでありGoogleエンジニアでもあるomoさんのエッセイもすばらしいです。

死んでしまったOSたちへ – To Phantasien