かまたま日記3

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

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

embulk-input-remote v0.4.1 リリース

約1年ぶりのリリース、0.4.0はGemはリリースできたのですが、CircleCIからの自動タグ付けに失敗したので新しいのをリリースしました..w
0.3系から仕様は変わってませんがEmbulk 0.9にあげたりKotlinとかその他の依存ライブラリやGradleのバージョンを全部最新にしました。