2017年まとめ

キャリア

  • Bit Journey(as エンジニア)とSpeee(as 技術顧問)は引き続き
    • スタートアップでサービス開発するのは学びがあってよい
    • 技術顧問業は難しい。成果はあったりなかったりする
  • 35歳になった
    • 2017年の初頭に 定年説をめぐって — 1. ミームの濫用 – To Phantasien *1 を読んでキャリアについて考えた
      • ぼくはこれを「35歳定年説は捨てていいとして、その後50代や60代を見据えたキャリアプランを真面目に考えるべきでは?」という話だと解釈した
    • 自分に関して言うと、まだ Plan B(≒ プログラマ以外の職種)は考えられないが…
  • 子供が産まれた
    • 家事と子育てに割く時間が非常に大きい
    • これは好むと好まざるとに関わらず、キャリアに対する影響は大きい
      • 時間もそうだが、家事と子育てで疲労がたまることにより他のことをする気力がなくなるという問題もある
        • 結果、子供が産まれてから個人プロジェクトの進捗がほぼゼロに
    • 子供が産まれたのは嬉しいし、家事も子育てもやぶさかではない
      • ただキャリアとの兼ね合いが難しいのは確かだと思う
      • 現職はwork from home可能なので、家庭と仕事の両立という点ではだいぶ恵まれてはいる
    • こういうときこそ write code every day

技術的なアクティビティ

*1:これは三部作の第一部で、どちらかというと第二部、第三部のほうが重要。

GraphQLの基本用語

GraphQLって用語が分かりにくいんですよね…ということで社内用に作った用語集を公開しておきます。

  • GraphQL http://graphql.org/
    • グラフキューエル
    • query language
    • リクエストのフォーマットがGraphQLということ
    • レスポンスはJSON(でもMessagePackでもなんでも)
  • GraphiQL https://github.com/graphql/graphiql
    • グラフィクル
    • GraphQL用のAPIコンソールというかAPIエクスプローラとかそういう類のもの
      • 補完のサポートを受けながらqueryを書けるので "GraphQL IDE" とも呼ばれる
    • 実体はReactベースのウェブアプリ(フロントエンドアプリ)
      • graphiql-rails はこのフロントエンドアプリをRails Mountable Engineとして扱うためのgem
    • GitHubのGraphQL APIもGraphiQL製: https://developer.github.com/v4/explorer/
  • Schema
    • GraphQLのスキーマ定義。Rails/DBのスキーマとは別
  • Type
    • GraphQLの型定義。RubyのクラスやModelとは別
  • Relay https://facebook.github.io/relay/
    1. Facebook製 UI framework
    2. ↑が要求するAPI仕様
      • サーバーサイドで「Relay」という場合はだいたいこちらで
      • Relay (1) を使わなくてもRelay (2)には準拠しておくとよい
  • Relay ID
    • Relay (2) が定めるIDのフォーマット
    • 単体でTypeとそのインスタンスIDを表す
    • 典型的な実装は base64encode([type_name, database_id]) という感じ
  • Relay Connection Type
    • Relay (2) が定めるリソースリストのフォーマット
    • ただのJSON配列ではなくラッパーオブジェクトを必須としているため、totalCountなどのカスタムフィールドも簡単に入れられる
    • cursorベースのpagination interfaceを提供
    • graphql-rubyだとAR::RelationやArrayを透過的にconnection型として扱える

caniuse.com が素晴らしいのでパトロンとして登録した

https://caniuse.com/ というサービスがあって、ブラウザごとのJSやHTMLの機能を調べられる(caniuse = Can I use X?) 超便利なサービスなのです。

自分のいるリージョンなども登録できて、ある機能を利用できるユーザーのシェアを数字でみられます。たとえばぼくはJapanとGlobalを表示するように設定していて、こんな感じです。

f:id:gfx:20171221134846p:plain

これによれば、2017年12月時点だとwebpはglobalだと70%、JPだと40%程度のブラウザで利用可能というわけですね。

で、超便利サービスだし寄付できないかなと思ってちょっとサイトを眺めると、subscriptionで $1/month の寄付を受け付けるコースがあるようです。Patreonというパトロン管理サービスを利用しているようです。せっかくなのでパトロン登録してみることにしました。これにより広告がなくなったり新機能をいち早く使えたりなどの特典もあるようですが、まあもともと十分に便利なのでそのへんはいいでしょう。

caniuseがひどく使いにくくなるなどの事情でもない限りはしばらく継続しようと思います。

High Sierraには「アプリのリンクを踏んでもChromeの空白ページが開くだけ」というバグがあるっぽい

一時的な回避策ですが、このバグが発現する状態になったら「Chromeを再起動する」で回復するようです。このエントリを書いている時点でのmacOSの最新版は 10.13.2 (High Sierra) なので、以降のバージョンでは直ってるかもしれません。

ぼく自身はまだSierraのままなので伝聞です*1。ただTwitterのタイムラインで頻繁にこれ系の悩みを見かけて、そのたびに返信しているのでもっと知られるべきだなと思い、キーボードを叩くことにしました。


追記: Chromeのアップデートがきているときに起きやすいようですが、アップデートが来ていなくても起きるようです。

*1:macOSはリリース後半年ほど様子をみることにしています。

railsdmでマルチテナント・ウェブアプリの話をしました

railsdm.github.io

発表:「マルチテナント・ウェブアプリケーションの実践」

一文でまとめると「HTTPのリクエストごと、あるいはjobの実行ごとにストレージの名前空間違うから気をつけような!!」ってのを常に意識する必要がありますって話でした。

なおElasticsearchのnamespacing v2はまさに先週の話なのですが、遅くなったというのは勘違いでした。 というのも少しだけ日付けをずらしてfuzzy searchの実験もしており、それがパフォーマンスに悪影響を与えていたようです。fuzzy searchはノイズが増えるので結局無効にすることにしたため、トータルではnamepsacing v2はnamespacing v1よりパフォーマンスがよくなっています。

またブコメでも指摘されているように、テナント横断のことをやり始めるとまたいろいろと新しい課題がでてくると思われます。楽しみですね😁

参考文献

おまけ

DroidKaigi Prelude というイベントでDroidKaigiのセッションを徹底解説します!

DroidKaigi 2018 (2/8-2/9)の関連イベント*1としてDroidKaigi Preludeというイベントを企画しました。現在、参加者&LTを募集中です。

connpass.com

メインセッションの「DroidKaigiセッション解説」を @shiraj_i さんと @muumuumuumuu さんといっしょにやります*2採択されたセッション が沢山あるのでどのセッションに参加するか迷うと思いますが、それぞれのセッションの魅力を余すところなく解説してますます迷ってもらおうという企画です😝

この企画ですが、 RubyKaigi 2017のRejectKaigiのセッションで「タイムテーブル徹底解説」というものがあり *3、これが非常にすばらしかったのでDroidKaigiでもぜひやりたいと思っていたところ、 @okoysmさんと@tarappoさんがイベントの開催をやっていただけるということで、無事 "DroidKaigi Prelude" というイベントとなりました。DroidKaigiの非公式イベントですが、DroidKaigi代表の mhidaka さんも一参加者としてきていただけそうです。

ところで個人的な話になりますが、最近子供がうまれまして*4平日就業時間後や休日のミートアップに行く機会が激減してきました。そんな折にこういうツイートを見て「これだ!!!」と思いたち、本イベントもプレミアムフライデーの 16時開催というスケジュールにしてもらいました。

この時間帯だと家庭があっても参加しやすいので、もっと増えてほしいですね*5

*1:非公式イベントなので主催はDroidKaigi実行委員会ではありません。

*2:たまたまですが、ぼくを含めて三人とも dex.fm のそれぞれ #33, #36, #37 で出演してますね。

*3:#rejectkaigi2017 を開催しました - Speee DEVELOPER BLOG

*4:ハンドルネームは "mfx" です。

*5:とはいえ月末の金曜はなかなか早く帰りづらいところがあると思うので、プレミアムフライデーを月初の金曜ということに再定義してほしい気がしなくもないですが、まあそれはまた別の話。

ISUCON7本戦で5位でした

ISUCON7 にチーム「スギャブロエックス」で参加して5位でした。

f:id:gfx:20171127210235p:plain

ベストスコアが29944点なので、せめてこれを最終スコアにしたかった…!

f:id:gfx:20171201150932p:plain

f:id:gfx:20171201150856p:plain

チームメイト

リポジトリ

github.com

所感

今回は "Chair Constructor Online" という ロジックが超複雑でサーバーのCPU負荷の高いリアルタイムオンラインゲーム ということで、非常に面白いテーマでした。BigIntが核心だったのもよくて、いちソフトウェアエンジニアとして地力を試された感じでした。運営の皆さん大変おつかれさまでした。

チームとしての動きはだいたいすぎゃーんが書いたとおりなので、ほかにいくつか特筆すべきことを私の視点から書きます。

NodeJSを選んだことについて

予選とおなじくNodeJSでした。

問題設定がWebSocket + BitInt だったのでNodeJSとの相性も悪くなさそうだし、予選でチームメンバーもNodeJSにある程度慣れたと思われるので問題はないだろうというところです。NodeJSでBigIntを触ったことがなかったのでライブラリを確認したところ、gmpの薄いラッパーだったのでこれも問題はないだろいうという判断でした。

https://github.com/kdawes/node-bigint

もっともこの実装は npmjs.org にリリースされていないようで、pakage.jsonのなかでrepository直指定でした。nodejs界隈ではあまりBigIntは使われないのかもしれないですね。

ちなみに tc39/proposa-bigintがstage 3でV8での実装も進んでいるようなので*1、来年くらいになればNodeJSでBigIntも普通に(あるいは --harmony-bigint 付きで)使えたのかもしれません。スコアには大きな影響はないでしょうが、四則演算子を普通に使えるのでコードを書くのは楽だったはず。

TypeScript化

すぎゃーんとkazeburoさんが計画を練っているときに私がコードをTypeScript化しました。コード自体は一瞬でできて、ついでにdeploy時にコンパイルしたりする仕組みも作ってミッション完了。30分くらいでできたと思います。

ESLintとTypeScriptどちらにするか悩んだのですが、変換さえしてしまえばTypeScriptのほうが書き味がよいこと、変換作業は直近でも何度かやっているのでそれほどハマらずにできる自信があったこと、などからTypeScriptを推すことにしました。

実際わりと好評だったと思います。

全体のストラテジ

2コアを活かすためにnodejsサーバのプロセスを増やすというタイミングで、kazeburoさんからは「systemdのサービスを2つ作ったほうがいいと思う」といわれたのですが、「面倒なので一旦cluster化します!!」という判断をしてしまいました。そして後にこれが大失敗だったなと思うにいたりました。

今回はroomごとにデータが完全に独立しているので、1サーバにウェブアプリとMySQLを同居させるという構成で分散させています。ここで最初は1サーバ1アプリプロセスだったので、他のroomとデータが干渉せずmutable dataでも安全にメモリキャッシュが行える状態でした。

ここでさらにnodejs1とnodejs2がそれぞれ別のポートをlistenすれば、そのアプリプロセスのなかでメモリキャッシュができたわけです。しかしclusterだとどのように分散するか不明なので、適切にパージが必要なmutable dataをキャッシュできません。なのでメモリキャッシュがimmutable dataに対してしかできないという制約があるまま最後までいくことになってしまったのでした。これは完全に私のやらかしです。ここでめんどくさがってはいけなかったのだ…。

NodeJSのプロファイラ

これは完全にNodeJSを選んで失敗だったのですが、なんとプロファイラが使えませんでした。正確には、プロファイルデータは出せるものの整形コマンドが数十分まっても戻ってこないため、結果をみれませんでした。

なのでCPU時間については測定せずに最適化するという苦しい戦いでした。これについては別途しらべないといけないですね…。

全体

予選・本戦と大変楽しく過ごせました。バランスの良いチームだっただけに、優勝できなかったのは残念極まりない…!!

来年は問題の移植作業を手伝いたいので参加はしないつもりですが、またいつかこのチームで本戦に参加できたらと思います!

ES modulesのexport defaultは使わないほうがよい

ES modulesにexport defaultってのがあるんですが、default exportのexport対象に名前が必須でないため、IDEによるコード補完と相性が悪いです。

他のところはどうしてるのかなと思って調べてみると、GoogleのTypeScript Style Guide では禁止されてました(v1.0.0, 2019/07 現在)。

TypeScript compiler coding guidelineには特に言及はないみたいですね。

そもそもexport defaultは仕様が不安定で処理系によって挙動が異なる困ったヤツ*1*2という認識です。

総じて見るとメリットは特になくデメリットがいくつかあるという状態なので、「使わない」とするのがよさそうな気がします。


(追記 2019/07/23)

"TypeScript Deep Dive" というTypeScript解説書でも default export を避けるべきという章が設けられてます。理由はこのエントリで説明したものとほぼ同じです。

*1:うちだとBabelからTypeScriptに移行するときに import React from "react" を import * as React from "react" に書き換える必要がありました。このケースで誰が悪いのかは調べてませんが…。

*2:追記: TypeScript 2.7 でこの部分はCommonJSとの相互運用できる変換オプションが追加されたようです (--esModuleInterop) https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html

nodejsの標準モジュールzlibはworker threadを使うらしく

Zlib | Node.js v9.2.0 Documentation

Note that all zlib APIs except those that are explicitly synchronous use libuv's threadpool, which can have surprising and negative performance implications for some applications.

スレッドプール数は UV_THREADPOOL_SIZE 環境変数で変えられて、ドキュメント によてばデフォルトは4とのことです。なお同期メソッドの場合はスレッドは使われないとあります。また fscrypto の一部のメソッドも同様にworker threadで実行されるようです。

考えてみればCPUバウンドなzlibを単に非同期にするのは意味がないわけですが、この暗黙のworker threadのことは全然知らなかったので驚きました。

WebAssemblyをNodeJS Native Addonの配布形式として使う - Islands in the byte stream みたいなことをする場合はこのworker threadは使えないので、たとえば単にnodejsの標準モジュールzlibをwasmに置きかえるだけだとマルチコアを活かせないぶん不利になる可能性がありますね。

WebAssemblyをNodeJS Native Addonの配布形式として使う

三行まとめ

  • Cライブラリzopfliをwasmにビルドして npmjs.com にリリースしてみた
  • wasmはポータブルなバイナリで、ネイティブコードと比較して半分程度の性能を期待できる
  • emscriptenは N-API と比べると出来ることが少なすぎるのが課題

背景

WebAssembly *1 の評価のために、Cで書かれたzlib互換の圧縮ライブラリ google/zopfli をemscriptenでwasmにビルドしてリリースしてみました。

評価だけでなくリリースまでしたのは、zopfliのnodejs native addonである node-zopfli をしばしばインストールできないことがあるという問題があってそれを解決したかったからです。nodejsのnative addonはnode-gypというビルドツールを使うことが多いようですが、これはPython 2.x などnodejsのエコシステム外のツールを利用するため、npm packageのインストールが難しいことがあるのです*2

WebAssemblyにビルドしたものを配布するのであれば、実質的にnaive addonに実行可能形式を含めてnpmjs.orgでま配信するということになり、nodejs native addonのもつビルド問題とは無縁でいられます。これでWebAssemblyの速度が十分であれば、 Cで書かれたライブラリのbindingをマシンに依存しない移植性のある実行可能形式で配布する という夢のようなことを実現できるというわけです。

成果物

github.com

npm install -D "@gfx/zopfli" でインストールできます。 compression-webpack-plugin + zopfli でgzip圧縮ファイルを用意する - Islands in the byte stream で示した require("node-zopfli")require("@gfx/zopfli") に置きかえるだけで動くはずです。

ランダムなバイト列を対象にベンチマークをしてみたところ、以下のようになりました(READMEから抜粋):

payload size: 1

  • universal-zopfli x 106 ops/sec ±0.79% (80 runs sampled)
  • node-zopfli x 201 ops/sec ±1.99% (82 runs sampled)

payload size: 1024

  • universal-zopfli x 1.37 ops/sec ±12.99% (11 runs sampled)
  • node-zopfli x 4.62 ops/sec ±3.34% (27 runs sampled)

payload size: 1038336

  • universal-zopfli x 0.26 ops/sec ±6.91% (6 runs sampled)
  • node-zopfli x 0.39 ops/sec ±1.35% (6 runs sampled)

ペイロードサイズによってパターンが違いますが、native binding版の node-zopfli の 30%から70%くらいの速度が出ています。 zopfli に関していえばこのくらいの速度で十分なので、そのままリリースすることにしました*3。しかもこれはNodeJS/V8にWebAssemblyが組み込まれたごく初期のもので、WebAssembly自体まだMVP (Minimum Viable Product) でしかありません。速度はこれから向上していくことでしょう。

なおemscriptenの共通ランタイムがかなり大きく、minifiedで50kbもあります。今回はNodeJSライブラリとして主に使うことを想定しているので関係ありませんが、ウェブ用として考えるとwasmのために50kbがもれなくついてくるというのは小さくない欠点です。この意味でも、現状ではNodeJS用としてはすでに実用可能でも、ちょっとしたウェブ用ライブラリのためにwasmを使える状態ではなさそうです。

しかしNodeJS用の技術としては、WebAssemblyは将来的に移植性のある実行可能形式として非常に有効である可能性を示せたと思います。

Binding Code

さてWebAssemblyは移植性のある実行可能形式としてすでに実用可能ではありますが、現状だと開発が非常に難しいという問題があります。

まず今回は、ある程度枯れているemscriptenを使うことにしました *4

emscriptenでzopfliをビルドする試みはすでに 2013年に imaya/zopfli.js として存在するため、Zopfli を Emscripten で移植した際の備忘録 : document やzopfli.jsのソースコードなどを参考にしつつ開発しました。

しかし開発は正直いってかなり難しかったです。wasm (ソースコード的にはC言語の関数)に対してはポインタも含む数値しか渡せないので、JSの文字列をutf8にencodeしつつ整数の配列にして *5、それをemscriptenのヒープにコピーしてポインタ(=ヒープのオフセット)を得てそれを引数とし、戻り値はこれまたポインタをいじってヒープの内容を Uint8Array に変換し、最後に確保したヒープを freeで解放し…という具合です。いかにもオーバーヘッドがありそうなことばかりしてますね。うまくいかないときのエラーメッセージもわかりにくく、単にbindingを作るならN-APIのほうが開発はしやすいでしょう。

コードはこんな感じです。

binding.c はともかく、 index.ts には苦戦の後がしのばれると思います。

さすがにこのままだとつらいので、そのうちemscripten的にもWebAssembly的にも何とかなるとは思います。とはいえ現状でも文字列(またはバイト列)を受け取りバイト列を返すことはがんばればできるので、すでにそれなりに面白いことはできそうです。

今後の展開

WebAssemblyは Web と付くのでブラウザ専用技術かと思いきや、NodeJSでもかなり有用な技術になる可能性があります。現時点でもNodeJSはスクリプト言語として高速な部類であり、WebAssemblyはそれを補完する技術として期待が持てます。

NodeJSの欠点としてよく語られてきたRDBMSと相性が悪いという特徴は、Promiseとasync/awaitによって解消されました。静的型がないという欠点も、TypeScriptによってある程度解消し、大規模な開発にも耐えられるようになりました。そしてこのWebAssemblyによって、一部の重い処理をCで実装しつつさらにそれをサーバーとウェブブラウザ両方で利用したりするということもできるようになりつつあります。これだけ好条件がそろえば、 NodeJSは今後、ウェブアプリケーション開発用言語として有力になっていくでしょうね!

そういうわけで、WebAssemblyは非常に注目している技術なのです。

*1:See also WebAssembly の基礎 - nmi.jp

*2:node-zopfliはさらにnode-pre-gypというビルド済バイナリをクラウドストレージに保存して利用可能であれば利用する、という仕組みなのですが、これのどこかでコケていてインストールできないことがあるようでした。ただし今回はnode-pre-gypには関心がないので深入りはしてません。

*3:ただしWebAssemblyが利用可能であるNodeJS 8.0以上が必要です。

*4:他、LLVMでwasmを生成したり Cheerp を使うという方法もあるようですが、現時点で一番安定しているのはおそらくemscriptenです。

*5:これはemscriptenが提供するユーティリティ intArrayFromString() でできますが…。