かまたま日記3

プログラミングメイン、たまに日常

アメリカに行ってきた

先週1週間会社の出張でアメリカ(マウンテンビュー)に行ってきました。海外は前職でのフィリピン出張以来2年ぶりです。

飛行機

行きは羽田からサンフランシスコ国際空港(SFO)の直行便で9時間ほど、帰りは後述の理由からSFOから香港国際空港(HKG)経由の成田への便でした、香港での待ち時間含めて22時間くらい。中国は初上陸でしたが、HKGでWi-Fi使う分には普通にTwitterできました。

気候

滞在した一週間は雨も降らず、サンフランシスコもマウンテンビューも最低10℃最高25℃くらいで朝は肌寒く日中は過ごしやすい、過ごしやすい気候でした。"ベイエリアは湿度が低いので日本ほど暑く感じない" というのをよく聞いていましたが、実感しました。

食事

基本的にアメリカ基準のサイズですべてが大きかったです。会社のランチはEAT Clubというのを使っているのですが、サラダ+主菜で注文したらサラダが直径25cmくらいのボウルに並々入っていて単体でお腹いっぱいになる量でした。結局食べきれず、持って帰って夜食べていました...w

あと、マウンテンビュー最後の夜にSushitomiという店で寿司を食べましたが、日本のクオリティで普通に美味しかったです。近くのshalalaというラーメン屋も美味しいらしいので、今度MVに行った際は寄ってみたいです。

仕事

1週間と短い間の滞在で時差ボケもありあまり効率的に作業ができたとは言いがたかったですが、直属の上司やプロダクトマネージャー、QAチームなどと初めて直接顔を合わせてコミュニケーションできたので、今後の仕事のやり易さに繋がればなあと思います。

歯の詰め物が取れた

3日目に歯の詰め物が取れてしまいました。治療したかったのですが、HRの方とも相談して日本に帰国後治療することに。IT健保の場合、海外での医療費は後で申請すれば返金してもらえるようなのですが*1、日本の治療費基準で返金されるようなので、日本の3~10倍ほどすると言うUSでの歯科治療費を考えると、申請する手間と割にに合わないと判断しました。

Uber, Airbnb

今回の出張で初めてUberAirbnbを利用したのですが、なるほどこれは便利だなあという感じでした。特にUberは精算の手間も無くなるし日本でもUberもしくは同様なサービスが流行ってほしいなと思いました。

コンピュータ歴史博物館

マウンテンビューを経つ直前に行ってきました。そろばんから現代のコンピュータまでの計算機の歴史が一同に介しており、とても見ごたえがありました。時間がなく駆け足で見ることになってしまったので、また行ってじっくり見たいです。

余談

出張後に夏休みを取って、家族とアメリカ観光をする予定だったのですが、直前になり子供が熱を出して渡航できなくなってしまうと言うハプニングが。結局諸々の予定をキャンセルし、帰りの航空券も取り直し日本に帰ることになりました。ホテル、ツアーなど直前キャンセルが利かないものも多く、10%ほどしかお金は戻ってきませんでした(泣)

tfenvのオーナーを移管した

色々issueとかPRとかもらっていたのですが、転職も決まってTerraformユーザーではなくなってしまったこともあり、私自身がメンテナンスするモチベーションが上がらなくなっていたため、別の方にオーナーになってもらうことにしました。

2ヶ月ほどの募集期間と話し合いを経て、前からコミットしてくれていたZordrakさんに移管することになりました。

300以上のスターをもらって世界各地の人からissueやPRをもらうようなOSSを作るというのは光栄で、いい経験になりましたが、OSSを個人でメンテし続けるということの難しさも実感しました。唯一残念なのは、移管することで自分のGitHubプロフィールがスター数的に寂しくなってしまったことです(笑)

オーナーではなくなりましたが、今後もtfenvをご贔屓にしていただけると幸いです。

CircleCIでgradle testがOOMで落ちるのを防ぐ

とあるプライベートなリポジトリで急にCircleCI上のgradleのテストが落ちるようになってしまい、レポートのxmlも出力されなくなってしまいました。

その時のログの一部

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> Process 'Gradle Test Executor 1' finished with non-zero exit value 137

* Try:
Run with --debug option to get more log output.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':test'.

テストのプロセスが exit value 137 で落ちているとのこと。調べてみるとOOMっぽい感じです。

Exit code 137 - Out of memory – CircleCI Support Center

確かに、今回テストの数をParametarizedを使って倍に増やしたので、それが原因っぽいです。

Gradle自体はこちらによるとデフォルトでは1024MBヒープサイズを確保するようなのですが、 test タスクはビルドプロセスとは別のJVMを使うようになっており、そこにはこの制限が適用されないようです。そこで test タスクのmaxHeapSizeを使って最大値をCircleCIのデフォルト(4GB)を超えないよう調整します。

参考:
Testing in Java & JVM projects - Gradle User Manual
Test - Gradle DSL Version 4.8.1

test {
    // Not to exceed the limit of CircleCI (4GB)
    maxHeapSize = "3g"
}

これで落ちずにテストを完遂できました。

Chromeで特定のドメインのアクセスをリダイレクトさせる

Twitterのブックマーク機能は現在PC版のサイトでは提供されておらず、mobile.twitter.com を使ったモバイル版でアクセスする必要があります。

An easier way to save and share Tweets

Bookmarks are now rolling out globally on Twitter for iOS and Android, Twitter Lite, and mobile.twitter.com.

なので、外部のサイトから特定のツイートのリンクを踏んでそれをブックマークしたいとなると、ブラウザバーでドメインmobile. 加えてという作業が発生します。いちいち面倒だったので、ブラウザの設定でリダイレクトできないか色々調べると、こちらの記事が当たりました、2018-6-28公開、タイムリー!

www.lifehacker.jp

ということで、最初に紹介されているSwitcherooを今は使ってます。設定はこんな感じです。

f:id:kamatama_41:20180701232636p:plain

最初は from: twitter.com to: mobile.twitter.com としていたらうまく動きませんでした。部分一致で探しているようで mobile.twitter.com でアクセスしたときも mobile.mobile.twitter.com にアクセスしようとしているようでした。最終的にURLスキームをつけることで解決しました。

続きを読む

Parallel Streamの並列数を調整する

Streamの parallel メソッドを呼ぶとストリームの処理を並列に実行できますが、これは内部的には前回紹介したForkJoinPoolが使われています。ForkJoinPoolは内部でcommon poolと呼ばれる共通プールを持っており、明示的にPoolを指定しない ForkJoinTask#invoke などはこのプールが利用されるようです。

public static void main(String[] args) {
    IntStream.range(0, 16).parallel()
            .forEach(i -> System.out.println(Thread.currentThread().getName() + ": " + i));
}

自分のマシン (MacBook Pro 15 inch) ではプールのサイズは7でした。実装を見るとデフォルトでは "コア数-1" になるようです。

main: 10
ForkJoinPool.commonPool-worker-3: 2
main: 11
ForkJoinPool.commonPool-worker-3: 3
ForkJoinPool.commonPool-worker-3: 1
ForkJoinPool.commonPool-worker-4: 13
ForkJoinPool.commonPool-worker-4: 12
ForkJoinPool.commonPool-worker-1: 5
ForkJoinPool.commonPool-worker-1: 8
ForkJoinPool.commonPool-worker-4: 15
ForkJoinPool.commonPool-worker-3: 6
ForkJoinPool.commonPool-worker-6: 0
main: 9
ForkJoinPool.commonPool-worker-7: 4
ForkJoinPool.commonPool-worker-2: 14
ForkJoinPool.commonPool-worker-5: 7

これは、システムプロパティ -Djava.util.concurrent.ForkJoinPool.common.parallelism で変更可能で、試しに2にしてみると、こんな感じの出力になります。

main: 10
main: 11
ForkJoinPool.commonPool-worker-1: 4
ForkJoinPool.commonPool-worker-1: 5
ForkJoinPool.commonPool-worker-0: 2
ForkJoinPool.commonPool-worker-1: 6
ForkJoinPool.commonPool-worker-1: 7
main: 8
main: 9
ForkJoinPool.commonPool-worker-1: 0
ForkJoinPool.commonPool-worker-1: 1
ForkJoinPool.commonPool-worker-0: 3
ForkJoinPool.commonPool-worker-1: 12
ForkJoinPool.commonPool-worker-1: 13
main: 14
main: 15

また、ParallelStreamで自前のForkJoinPoolを使いたい場合は、少しトリッキーですが、ストリーム処理自体をRunnableでラップしてForkJoinPoolに渡すと言った方法が使えるようです。 参考リンク

public static void main(String[] args) throws Exception {
    ForkJoinPool pool = new ForkJoinPool(2);
    pool.submit(() -> IntStream.range(0, 16).parallel()
            .forEach(i -> System.out.println(Thread.currentThread().getName() + ": " + i))
    ).get();
}
ForkJoinPool-1-worker-1: 10
ForkJoinPool-1-worker-0: 5
ForkJoinPool-1-worker-0: 4
ForkJoinPool-1-worker-0: 7
ForkJoinPool-1-worker-1: 11
ForkJoinPool-1-worker-1: 9
ForkJoinPool-1-worker-0: 6
ForkJoinPool-1-worker-0: 2
ForkJoinPool-1-worker-1: 8
ForkJoinPool-1-worker-1: 14
ForkJoinPool-1-worker-1: 15
ForkJoinPool-1-worker-0: 3
ForkJoinPool-1-worker-0: 1
ForkJoinPool-1-worker-1: 13
ForkJoinPool-1-worker-1: 12
ForkJoinPool-1-worker-0: 0

ForkJoinPoolについて

ForkJoinPoolJava 7から導入された新しいExecutorのフレームワークです。 旧来のExecutorと違うのは、タスクのスケジュールのアルゴリズムとして、work-stealingを採用していることです。これは再帰処理やタスクの中で更に細かな子タスクが生成されるような計算処理に適しています(例えばWebクローラなど)

ForkJoinkPoolに登録されている各ワーカースレッドは、それぞれワーカーキュー(実際はLIFO型のスタック)を持っていて、ForkJoinTaskを積むことができます。ForkJoinTaskは外部からForkJoinPoolの execute, invoke, submit メソッドを使って登録したり、もしくはタスクの中で直接別タスクを生成しその fork メソッドを呼ぶことで登録することもできます。forkされたタスクは join メソッドを使い、計算結果を待ちます。

ということで、WikipediaのWork stealingにあるモデルをForkJoinPoolとForkJoinTaskを使って実装してみます。ForkJoinTaskにはいくつかの抽象サブクラスがあり、今回はその中のRecursiveTaskを使います。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

class ForkJoinPoolExample {
    public static void main(String[] args) {
        int poolSize = Integer.parseInt(args[0]);

        ForkJoinPool pool = new ForkJoinPool(poolSize);
        int result = pool.invoke(new F(1, 2));
        log("Result is " + result);
    }

    static class F extends RecursiveTask<Integer> {
        private final int a, b;

        F(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        protected Integer compute() {
            log(String.format("Start compute of f(%d, %d) = g(%d) + h(%d)", a, b, a, b));
            G g = new G(a);
            g.fork();
            sleep(1000);
            H h = new H(b);
            final int result = h.compute() + g.join();
            log(String.format("f(%d, %d) = %d", a, b, result));
            return result;
        }
    }

    static class G extends RecursiveTask<Integer> {
        private final int a;

        G(int a) {
            this.a = a;
        }

        @Override
        protected Integer compute() {
            log(String.format("Start compute of g(%d) = %<d * 2", a));
            final int result = a * 2;
            log(String.format("g(%d) = %d", a, result));
            return result;
        }
    }

    static class H extends RecursiveTask<Integer> {
        private final int a;

        H(int a) {
            this.a = a;
        }

        @Override
        protected Integer compute() {
            log(String.format("Start compute of h(%d) = g(%<d) + (%<d + 1)", a));
            G g = new G(a);
            g.fork();
            sleep(1000);
            int c = a + 1;
            final int result = c + g.join();
            log(String.format("h(%d) = %d", a, result));
            return result;
        }
    }

    private static void log(String message) {
        System.out.println(String.format("%tT.%<tL [%s] %s", System.currentTimeMillis(), Thread.currentThread().getName(), message));
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最初は処理順を確認するため、プールのサイズを1にしてみます。

$ java ForkJoinPoolExample 1
01:37:23.239 [ForkJoinPool-1-worker-1] Start compute of f(1, 2) = g(1) + h(2)
01:37:24.293 [ForkJoinPool-1-worker-1] Start compute of h(2) = g(2) + (2 + 1)
01:37:25.295 [ForkJoinPool-1-worker-1] Start compute of g(2) = 2 * 2
01:37:25.295 [ForkJoinPool-1-worker-1] g(2) = 4
01:37:25.295 [ForkJoinPool-1-worker-1] h(2) = 7
01:37:25.296 [ForkJoinPool-1-worker-1] Start compute of g(1) = 1 * 2
01:37:25.296 [ForkJoinPool-1-worker-1] g(1) = 2
01:37:25.297 [ForkJoinPool-1-worker-1] f(1, 2) = 9
01:37:25.297 [main] Result is 9

プールサイズが2以上の場合はforkされたタスクは空きスレッドがあれば順次消費されていきます。

$ java ForkJoinPoolExample 2
01:37:43.282 [ForkJoinPool-1-worker-1] Start compute of f(1, 2) = g(1) + h(2)
01:37:43.292 [ForkJoinPool-1-worker-0] Start compute of g(1) = 1 * 2
01:37:43.293 [ForkJoinPool-1-worker-0] g(1) = 2
01:37:44.300 [ForkJoinPool-1-worker-1] Start compute of h(2) = g(2) + (2 + 1)
01:37:44.300 [ForkJoinPool-1-worker-0] Start compute of g(2) = 2 * 2
01:37:44.301 [ForkJoinPool-1-worker-0] g(2) = 4
01:37:45.305 [ForkJoinPool-1-worker-1] h(2) = 7
01:37:45.306 [ForkJoinPool-1-worker-1] f(1, 2) = 9
01:37:45.306 [main] Result is 9

退職しました3

4月で約2年半所属した前職を退職しました。お世話になった皆さんありがとうございました。

これまで

SREチームとしてインフラ周りを幅広く見ていました。アプリケーション開発以外の仕事は大体やったと思います。一部ですが具体的には以下の様なことをやってました。

また、SREからロールチェンジを希望して半年ほどReactを使った新規のアプリケーション開発を経験させてもらったり、それに合わせてフィリピンに出張に行ったりしました。フィリピン出張は会社生活の中で一番印象に残ってる出来事です。

退職/転職の理由とか

一番大きなトリガーになったのは、昨年子供が誕生したことです。仕事以外のプライベートの時間がほぼ子育てに費やされることになり、勉強時間や趣味プログラミングの時間がが激減しました。その上で、今後の自分のキャリアをどうしたいかと考えたとき、インフラではなくもっと直接的なソフトウェア開発の方に重きを置きたいと考えるようになりました。また、経済面においても子供にかかる費用(消費財系, 保育料, 将来に向けた教育費, 第2子など)を考えると、今までより多くの給与をいただきたくなった次第です。

これから

5月からTD社のお世話になっています。Hosted EmbulkであるData Connector/Result Outputを提供しているIntegrationチームというところに所属しています。技術力や英語力的なところで足りない部分も多いと思いますが、早くバリュー出せるように頑張ります。

例のもの

http://amzn.asia/b8ubFYx