たごもりすメモ

コードとかその他の話とか。

RubyKaigi Takeout 2021でしゃべった

RubyKaigi Takeout 2021の途中ではあるけど、とりあえず自分の発表終わったのでメモ。

Ractorをワーカーとして使うアプリケーションサーバを作ったよ! という話なんですが、まあこれは話の入りというかそういうもので、実際の内容の中心はRactorではどういうコードが動かないのか、どうすればよいのか、みたいな話です。 で、Ractorで動かないコードは全体的には直していきたいよねということになると思いますが、そうはいっても動かしてみないと確認もできないじゃんということで、主な問題であるところのWebアプリケーションを実際にRactorの上で動かして確認できるようにしておきましたよ、というのが今回作ったright_speedです。

github.com

本当はRactorの数を増やしていくごとにどれくらいのリクエストを処理できるようになるのかとか、MJITを有効にしたときその傾向はどうなるのかとか試したかったんですが、それよりだいぶ手前のところでSEGVを踏んでしまって、セッション録画の提出日までにはちょっとどうにもできませんでした。残念。

副産物

新しい機能なもんで作業してる途中であれこれあって、明確なやつはbugsにいくつか報告してます。

あとそのへんについてのパッチとか。

SEGVってるやつは見てみてはいるんだけど明確にこれかなというのはまだ分かってない*1んで、どうにかなるかなあ。あとRactorまわりの話はいじってると割と簡単にプロセスがビジー状態になったりSEGVったりするのが厳しい。

フィードバック

定数に値を入れるときにデフォルトでshareableだとマークする方法が欲しい、マジックコメントかな? という議論がスライド中であるんだけど、発表中にコメント欄で@ko1さんに教えてもらった。

# shareable_constant_value: true

class Foo
  VALUE = {key: "value"}
end

Ractorのドキュメントにも書いてあったので完全に自分の見落としでした。すいません。

(追記) …… と思ったら今日直されていたのでたぶんドキュメント読んだときはmagic commentだと思わずそのままスルーして忘れてたんだな。無実だったことにしよう。

*1:デバッグ用のカウンタまわりなんじゃないかなと思ってはいるんだけど

Amazon SESのメール受信は特定のリージョンでしか使えない

メモ。ap-northeast-1のコンソールでSES開いてても"Email Receiving"の"Rule Sets"が灰色になった(gray outした)ままで選択できなくて、ドメインのverifyとかIAMとかで何かうまくいってない? と調べてまわってた。

結論としては特定のリージョンでしかサポートされてない。

Email Receiving Endpoints

Amazon SES doesn't support email receiving in the following Regions: US East (Ohio), US West (N. California) Asia Pacific (Mumbai), Asia Pacific (Seoul), Asia Pacific (Singapore), Asia Pacific (Sydney), Asia Pacific (Tokyo), Canada (Central), Europe (Frankfurt), Europe (London), Europe (Paris), Europe (Stockholm), Middle East (Bahrain), South America (São Paulo), and AWS GovCloud (US).

docs.aws.amazon.com

なんだよもー。us-east-1かus-west-2かなあ。
なおリージョンが変われば "Verify a New Domain" もやりなおしみたい。

退職します2021

TL;DR

  • 現職のTreasure Dataを本日を最終出社として退職します
  • しばらくは休みをとりつつ次に何をやるかを考えるつもり
  • 次は自分でビジネスを立ち上げるか、それともエンジニアリングチームを作るところにフォーカスするか、これから考える
  • 技術顧問業もはじめます、が、メインにはしないつもり
  • その他これからの活動にご期待ください

現職について

就職時にこのエントリを書いてから6年3ヶ月、当初思っていたより長く働いたなあという感じです。入ったときはUSと日本で合計40人もいなかったくらいだったと思うけど、今では世界中に同僚がいて規模は約10倍くらいになりました。途中Armによる買収もあって、スタートアップから中規模企業までのビジネスと会社の成長を見てきました。自分もそれなりに貢献できてたんじゃないかなと思います。 いま見直すと就職エントリに書いていた3点、「技術ベンチャーであること」「ベンチャー企業として大きな成功を狙っていること、またそれが有望に見えること」「優秀なプログラマが同僚に多いこと」はすべて見事に満たされていて、データ処理に関する高い技術をベースにビジネスを組み立て、それにより成功したexitを経験できました。優秀なプログラマ大勢と働けていたのは言うまでもありません。

いやー、いい6年余りでした。

特に初期の頃は営業やSE*1、果てはBizDev*2担当のVPなんかとも一緒に仕事をすることがあったりして、顧客を訪問しにベイエリアやシアトル、スコットランドなんかに行って直接話したりもしました。

ここ3〜4年くらいは自分のマネージャや同じチームの同僚が基本的に北米の人ばっかりという状態で、おかげで英会話の機会には困りませんでした。入社時は英会話はうーん、がんばる! くらいだったんですけど、今はもうペラペラ……というと語弊があるけど、日常会話から業務上の議論や交渉、練習なしのプレゼンテーションまでだいたい困らなくなりました。英語圏のIT系企業ならたぶんどこでも働けるくらいにはなれたんじゃないかなと思います。会社から英会話サービスの補助とかも出てたけど、結局まったく使わずに終わってしまった*3

他はもちろん、お金は大事で、もう非常によい目を見させてもらいました。Treasure Dataの創業者たちがインタビュー等でよく開発者への待遇や見返りについて話していますが、正直彼らのやったことはインタビューで言ってる内容でもまだだいぶ過少に言ってるなという感じで、考えられる限りによい報酬プログラムにしてもらったなと思います。本当に感謝しています。

じゃあなんで辞めるの

単純に次に何をやるか考える時間が欲しくなった、という感じです。Armによる買収の後から「次になにやるの?」という質問が脳内をぐるぐるしていて、けれど普段の業務をやっているとそういうことを考える時間や余裕がなかなか取れないので、いったん休んでそういうこと考えてもいいかという気分になりました。ありがたいことに、金銭的には多少の余裕はあるし。

まあ他にもいち開発者として働いている今のポジション/ロールを変えたくなったこと、会社の規模が大きくなってきたことにより自分がTreasure Dataという組織に求めているものと実態との間のギャップが大きくなってきたこと、なんかもあります。特に自分の役割というか仕事としてやることの内容については、前までは一生いち開発者でいいと思っていたんですが、この6年経験したことを考えると、もうちょっとやりたいことも色々あるなあというか。このへんはまだはっきりしたことが考えられてないので、これからですが。

次どうするの

とりあえず数ヶ月はゆっくり休みをとりつつ考えようかと思っています。現状、次のフルタイム仕事についてはどことも何も話をしていません*4

が、ちょうどよいタイミングで来た話がありまして、6月からはあるスタートアップの技術顧問を1件引き受けることにしました。技術顧問業を生業にしようとはあまり思っていないのですが、それでも自分の知識と経験を役立てられそうなこと、色々な会社やサービスの実態を見せてもらうのは今の自分の考え事にとっても非常に大きいプラスになりそうなことから、とりあえずやってみようと思っています。 ということで、他にももしお困りのところに自分がお役に立てそうなところがあればご連絡ください。もう数件くらいは引き受けられるかもしれません。

次のメインの仕事はどうするかこれからなんですけど、どちらかというと開発者としての仕事よりは、チームを作る・ビジネスを作る、といった方向かなあとぼんやり考えています。明確なイメージはまだないんですが、どうしたものか。先達の方々に話を聞きに行きたいなあと思っているので、そのうち皆様にお願いに上がるかもしれません。その際はよろしくお願いします。

あとは最近読めてない本を読みまくったり、あまり触ってない方面の言語をやってみたりしつつ、ちょっとは遊んだりします。他にボランティアで参加する活動なんかもありそう。コロナが無ければ世界中旅行して回ったりしたいところなんですけど、それはまた今度の機会かな、しょうがない。飲みにいったりもできると良かったんだけど、コロナ明けまで持ち越しですね。

ということで

今後ともtagomorisをよろしくお願いします!

*1:Sales Engineer

*2:Business Development、日本語で言うの難しいなこれ

*3:というか大学を出て以来、英会話教室的なものには一度も通ったことがない。義務教育と受験英語はなかなかよくできている

*4:実は最近どうかと思える件もあったんだけど、詳しい内容を聞くとこちらの期待との間にギャップがあったのでそのお話は無かったことに

Gradle経由でのテスト実行時、コンソールに失敗したテストケースの情報を出力する

`./gradlew test`とか`./gradlew build`とかしたときに失敗したテストの情報はこの`index.html`を見てね! っていうのがめんどくさくて、なんでデフォルトで失敗したテストの情報を出してくれないんだっけ、と思っている人、主に俺の問題を解決する。
ていうかデフォルトでそうしてくれればCIサービスの設定時にテスト結果の保存とかを個別にやらないと何が失敗したかもわかんなくなってて困る、みたいな状態にならなくてすむのにね。

調べてたら「とにかくコンソールに全部出したい!」みたいなのがいっぱいひっかかるけどそうじゃないんだよなー、そういうのはtoo muchなんだよ。
で、これ。

stackoverflow.com

のうち一番下の回答(TestLoggingを使う)が簡単に調整できそうだったので、手元プロジェクトで以下のようにした。Kotlin DSL使ってるんで`build.gradle.kts`の変更。

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

// ...

tasks {
    "test"(Test::class) {
        // ...
        testLogging {
            events(TestLogEvent.FAILED, TestLogEvent.SKIPPED)
            exceptionFormat = TestExceptionFormat.FULL
            showCauses = true
            showExceptions = true
            showStackTraces = true
        }
    }
}

標準出力とか標準エラー出力とかの表示を抑制して失敗したテストケースの原因とスタックトレースを出す。やってみたらこんな表示になって自分の期待したものはこれだったんだ!!!!!!!!!!!! という感じ。

MBA:my-project tagomoris$ ./gradlew build

> Task :test

com.treasuredata.myproject.FooBarTest > should fail FAILED
    java.lang.AssertionError: expected:<1> but was:<0>
        at org.junit.Assert.fail(Assert.java:89)
        at org.junit.Assert.failNotEquals(Assert.java:835)
        at org.junit.Assert.assertEquals(Assert.java:120)
        at kotlin.test.junit.JUnitAsserter.assertEquals(JUnitSupport.kt:32)
        at kotlin.test.AssertionsKt__AssertionsKt.assertEquals(Assertions.kt:57)
        at kotlin.test.AssertionsKt.assertEquals(Unknown Source)
        at kotlin.test.AssertionsKt__AssertionsKt.assertEquals$default(Assertions.kt:56)
        at kotlin.test.AssertionsKt.assertEquals$default(Unknown Source)
        at com.treasuredata.myproject.FooBarTest.should fail(FooBarTest.kt:72)

97 tests completed, 1 failed

> Task :test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/tagomoris/td/my-project/build/reports/tests/test/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s
17 actionable tasks: 1 executed, 16 up-to-date

macOS Big Sur なM1 MacのEmacsでDocuments以下のファイル(など)を開く

M1 mac (macOS Big Sur)買ってからまだちゃんと開発とかに使えてなくてちょっとずつセットアップ進めてるんだけど、Emacsで ~/Documents 下とかが読めないことに気付いた。以前のバージョンで指摘されてた解法であるところの以下の方法もダメ:

  • M-x ns-open-file-using-panel で一度Documents下のファイルを開けばいける → ダメ
  • EmacsにFull Disk Access権限を与える → ダメ
  • 実は裏技で/usr/bin/rubyにFull Disk Access権限を与える → なんかファイル開くダイアログで/usr/binとかが開けなくなってない?(ダメ)

というわけで調べてたらこんなのが見付かった。

braveam.com
Can not do dired on ~/Documents when it is in iCloud on Catalina · Issue #84 · caldwell/build-emacs · GitHub

このままやるとx86_64なバイナリを起動しようとしてしまうので以下のように。
1. システム環境設定 → システムとセキュリティ → プライバシー → フルディスクアクセスをEmacs.appに与える
2. Terminalで以下のコマンドを実行、arm64なバイナリにリンクするよう注意

$ cd /Applications/Emacs.app/Contents/MacOS
$ mv Emacs Emacs.old
$ ln -s Emacs-arm64-11_2 Emacs

これでうまく動くようになった。めんどくさいですね。

Dropwizard ValidatorがListに対して正常に動かない

Kotlinが悪いのかListはそもそもダメなのか既知の問題なのかなどはまだ確認してない。

追記: この問題っぽい。JVMのオプション追加でどうにかなるケースと、いややっぱダメみたいな話もあるのかな。トップレベルにListを使わないのが結局のところいちばんよさそう。うーん。

Dropwizard Validator*1を使って、YAMLから読んだデータのバリデーションをしようと思ったんだけど、なんかどうやってもエラーが報告されなくておっかしいなーと思ってたところ、どうもトップレベルがListだと正常に動かないっぽい。
以下のようなテストコードを書いて確認できる。

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import io.dropwizard.jersey.validation.Validators
import javax.validation.Valid
import javax.validation.constraints.NotEmpty

val validator = Validators.newValidator()
val mapper = ObjectMapper(YAMLFactory()).registerKotlinModule()

data class TopLevel(
    @field:Valid val items: List<Item>
)

data class Item(
    @field:NotEmpty val data: String
)

class ValidationTest {
    @Test
    fun `validator does not report errors for top-level list`() {
        val yaml = """---
        |- data: ""
        |- data: ""
        |""".trimMargin()
        val list = mapper.readValue(yaml) as List<Item>
        assertNotEquals(emptySet(), validator.validate(list))
    }

    @Test
    fun `validator can report errors if top-level is not list`() {
        val yaml = """---
        |items:
        |  - data: ""
        |  - data: ""
        |""".trimMargin()
        val obj = mapper.readValue(yaml) as TopLevel
        assertNotEquals(emptySet(), validator.validate(obj))
    }
}

これでトップレベルがListのやつはエラーを報告せずfailし、トップレベルにdata classを置いてその子要素としてListを持たせたやつはバリデーションエラーを報告する。

f:id:tagomoris:20210209144401p:plain

条件を見付けるまでにだいぶ長いことハマってしまった。これ以上調べるかどうかはまだ決めてない。

*1:Hibernate由来の、Springとかでも使われてるやつ

DropwizardとShadow jarの話

AWS Lambda用にshadow jarをビルドしてたリポジトリで、別のデーモンプロセス*1をDropwizardベースにすることにして依存関係やビルド方法などを書き換えていったところ、設定ファイルに書かれてるLog appenderが見えない、みたいなエラーが出て起動に失敗するという状況に。手元で./gradlew runしたときはうまくいくんだけどサーバにデプロイしたらダメ。

io.dropwizard.configuration.ConfigurationParsingException: /opt/myapp/config.yaml has an error:
   * Failed to parse configuration at: logging.appenders.[0]; Could not resolve type id 'console' as a subtype of `io.dropwizard.logging.AppenderFactory<ch.qos.logback.classic.spi.ILoggingEvent>`: known type ids = [] (for POJO property 'appenders')
  at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.myapp.MyAppConfiguration["logging"]->io.dropwizard.logging.DefaultLoggingFactory["appenders"]->java.util.ArrayList[0])
         at io.dropwizard.configuration.ConfigurationParsingException$Builder.build(ConfigurationParsingException.java:278)
         at io.dropwizard.configuration.BaseConfigurationFactory.build(BaseConfigurationFactory.java:157)
...

なんかこんな感じのスタックトレースが出る。

調べてみたらDropwizardによる実装の探索とshadowが衝突しているっぽい。このへんのコメントを参考にした。
で、手元はGradleはKotlin DSLで書かれてるので、このコメントを参考にえいやとbuild.gradle.ktsに以下のようなのを追加。

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

// ...

tasks {
    named<ShadowJar>("shadowJar") {
        archiveBaseName.set("stream-processing-shadow")
        mergeServiceFiles()
        manifest {
            attributes(mapOf("Main-Class" to application.mainClassName))
        }
    }
}

これでうまくいった。めでたしめでたし。

*1:同じリポジトリの別Main classを使う