アメリカに行ってきた
先週1週間会社の出張でアメリカ(マウンテンビュー)に行ってきました。海外は前職でのフィリピン出張以来2年ぶりです。
飛行機
行きは羽田からサンフランシスコ国際空港(SFO)の直行便で9時間ほど、帰りは後述の理由からSFOから香港国際空港(HKG)経由の成田への便でした、香港での待ち時間含めて22時間くらい。中国は初上陸でしたが、HKGでWi-Fi使う分には普通にTwitterできました。
香港国際空港着、空港内のWi-FiからTwitterはみれる模様。
— かまたま (@kamatama_41) September 1, 2018
気候
滞在した一週間は雨も降らず、サンフランシスコもマウンテンビューも最低10℃最高25℃くらいで朝は肌寒く日中は過ごしやすい、過ごしやすい気候でした。"ベイエリアは湿度が低いので日本ほど暑く感じない" というのをよく聞いていましたが、実感しました。
食事
基本的にアメリカ基準のサイズですべてが大きかったです。会社のランチはEAT Clubというのを使っているのですが、サラダ+主菜
で注文したらサラダが直径25cmくらいのボウルに並々入っていて単体でお腹いっぱいになる量でした。結局食べきれず、持って帰って夜食べていました...w
あと、マウンテンビュー最後の夜にSushitomiという店で寿司を食べましたが、日本のクオリティで普通に美味しかったです。近くのshalalaというラーメン屋も美味しいらしいので、今度MVに行った際は寄ってみたいです。
仕事
1週間と短い間の滞在で時差ボケもありあまり効率的に作業ができたとは言いがたかったですが、直属の上司やプロダクトマネージャー、QAチームなどと初めて直接顔を合わせてコミュニケーションできたので、今後の仕事のやり易さに繋がればなあと思います。
歯の詰め物が取れた
3日目に歯の詰め物が取れてしまいました。治療したかったのですが、HRの方とも相談して日本に帰国後治療することに。IT健保の場合、海外での医療費は後で申請すれば返金してもらえるようなのですが*1、日本の治療費基準で返金されるようなので、日本の3~10倍ほどすると言うUSでの歯科治療費を考えると、申請する手間と割にに合わないと判断しました。
Uber, Airbnb
今回の出張で初めてUberとAirbnbを利用したのですが、なるほどこれは便利だなあという感じでした。特にUberは精算の手間も無くなるし日本でもUberもしくは同様なサービスが流行ってほしいなと思いました。
コンピュータ歴史博物館
マウンテンビューを経つ直前に行ってきました。そろばんから現代のコンピュータまでの計算機の歴史が一同に介しており、とても見ごたえがありました。時間がなく駆け足で見ることになってしまったので、また行ってじっくり見たいです。
US出張の最後に心ばかりの観光を (at @ComputerHistory Museum in Mountain View, CA) https://t.co/zOKJWTZ7Pt pic.twitter.com/y16DSZ97w9
— かまたま (@kamatama_41) 2018年8月31日
余談
出張後に夏休みを取って、家族とアメリカ観光をする予定だったのですが、直前になり子供が熱を出して渡航できなくなってしまうと言うハプニングが。結局諸々の予定をキャンセルし、帰りの航空券も取り直し日本に帰ることになりました。ホテル、ツアーなど直前キャンセルが利かないものも多く、10%ほどしかお金は戻ってきませんでした(泣)
tfenvのオーナーを移管した
色々issueとかPRとかもらっていたのですが、転職も決まってTerraformユーザーではなくなってしまったこともあり、私自身がメンテナンスするモチベーションが上がらなくなっていたため、別の方にオーナーになってもらうことにしました。
I'm looking for new owner or maintainers of #tfenv. Please let me know if you are interested in it, thank you!
— かまたま (@kamatama_41) April 12, 2018
tfenvの新しいオーナーかメンテナーを探しています。興味がある方は私まで何かしらの方法でメッセージを下さい。https://t.co/LeuG2Irboo
2ヶ月ほどの募集期間と話し合いを経て、前からコミットしてくれていたZordrakさんに移管することになりました。
もともとコラボレーターだったZordarkさんにtfenvのオーナーシップを引き継ぎました。 https://t.co/25bILLwccb オーナーではなくなりますが、今後共tfenvをご贔屓にしていただけると幸いです。
— かまたま (@kamatama_41) July 2, 2018
300以上のスターをもらって世界各地の人からissueやPRをもらうようなOSSを作るというのは光栄で、いい経験になりましたが、OSSを個人でメンテし続けるということの難しさも実感しました。唯一残念なのは、移管することで自分のGitHubプロフィールがスター数的に寂しくなってしまったことです(笑)
オーナーではなくなりましたが、今後もtfenvをご贔屓にしていただけると幸いです。
CircleCIでgradle testがOOMで落ちるのを防ぐ
とあるプライベートなリポジトリで急にCircleCI上のgradleのテストが落ちるようになってしまい、レポートのxmlも出力されなくなってしまいました。
うーん、CirclrCIでテストに失敗したんだけど例外のログが何も出ない、その上なぜかJUnitのレポートも作成されないしローカルでは成功する…原因が判らん…
— かまたま (@kamatama_41) July 13, 2018
その時のログの一部
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公開、タイムリー!
ということで、最初に紹介されているSwitcherooを今は使ってます。設定はこんな感じです。
最初は 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について
ForkJoinPoolはJava 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チームとしてインフラ周りを幅広く見ていました。アプリケーション開発以外の仕事は大体やったと思います。一部ですが具体的には以下の様なことをやってました。
- HerokuやKubernetes(on AWS)を使った、スケーラブルなアプリケーション実行基盤の構築/運用
- TerraformやAnsibleなどを使ってInfrastructure as Codeを実践
- tfenvもここで誕生しました
- CircleCIを使ったCI環境の改善
- 十数のレポジトリをCircleCI 2.0にマイグレートしたり、テストを並列実行してCI時間を短縮したり
- DatadogやNewRelicなどを使った監視、トラブルシューティング
- fluentdやEmbulk, tumugiを使ったデータ分析用のインフラの整備
- Shadow Proxyを使ったパフォーマンスチェック環境の構築
また、SREからロールチェンジを希望して半年ほどReactを使った新規のアプリケーション開発を経験させてもらったり、それに合わせてフィリピンに出張に行ったりしました。フィリピン出張は会社生活の中で一番印象に残ってる出来事です。
退職/転職の理由とか
一番大きなトリガーになったのは、昨年子供が誕生したことです。仕事以外のプライベートの時間がほぼ子育てに費やされることになり、勉強時間や趣味プログラミングの時間がが激減しました。その上で、今後の自分のキャリアをどうしたいかと考えたとき、インフラではなくもっと直接的なソフトウェア開発の方に重きを置きたいと考えるようになりました。また、経済面においても子供にかかる費用(消費財系, 保育料, 将来に向けた教育費, 第2子など)を考えると、今までより多くの給与をいただきたくなった次第です。
これから
5月からTD社のお世話になっています。Hosted EmbulkであるData Connector/Result Outputを提供しているIntegrationチームというところに所属しています。技術力や英語力的なところで足りない部分も多いと思いますが、早くバリュー出せるように頑張ります。